Loading libraries

Setting data location, make sure the files below are available so the rest runs.

datadir <- "../data"
list.files(datadir)
[1] "detroit-311.csv"                "detroit-blight-violations.csv" 
[3] "detroit-crime.csv"              "detroit-demolition-permits.tsv"
[5] "detroit-PARCELS.csv"           

First we load all data

Data permits exploration

head(data_permits)

First converting data fields to the right types

#converting to factors
cols <- c("CASE_TYPE", "CASE_DESCRIPTION", "LEGAL_USE", "BLD_PERMIT_TYPE",
          "PERMIT_DESCRIPTION", "BLD_PERMIT_DESC", "BLD_TYPE_USE", "RESIDENTIAL",
          "DESCRIPTION", "BLD_TYPE_CONST_COD", "BLD_ZONING_DIST", "BLD_USE_GROUP",
          "BLD_BASEMENT", "FEE_TYPE", "CSF_CREATED_BY","CONDITION_FOR_APPROVAL")
data_permits %<>% mutate_each_(funs(factor(.)),cols)
#converting $$ to numeric
cols <- c("PCF_AMT_PD", "PCF_AMT_DUE", "PCF_UPDATED","ESTIMATED_COST")
data_permits %<>% mutate_each_(funs(from_currency(.)),cols)
#converting to dates
cols <-c("PERMIT_APPLIED","PERMIT_ISSUED","PERMIT_EXPIRES")
data_permits %<>% mutate_each_(funs(parse_date_time(.,orders="mdy",tz="America/Detroit")),cols)
 3 failed to parse. 3 failed to parse. 3 failed to parse.
summary(data_permits)
  PERMIT_NO         PERMIT_APPLIED                PERMIT_ISSUED                
 Length:7133        Min.   :2010-05-20 00:00:00   Min.   :2010-05-20 00:00:00  
 Class :character   1st Qu.:2012-08-15 00:00:00   1st Qu.:2012-08-15 00:00:00  
 Mode  :character   Median :2013-03-12 00:00:00   Median :2013-03-12 00:00:00  
                    Mean   :2013-07-07 20:39:12   Mean   :2013-07-07 22:37:45  
                    3rd Qu.:2014-10-15 00:00:00   3rd Qu.:2014-10-17 00:00:00  
                    Max.   :2015-08-28 00:00:00   Max.   :2015-08-28 00:00:00  
                    NA's   :3                     NA's   :3                    
 PERMIT_EXPIRES                SITE_ADDRESS         BETWEEN1          PARCEL_NO        
 Min.   :2010-11-20 00:00:00   Length:7133        Length:7133        Length:7133       
 1st Qu.:2013-02-23 00:00:00   Class :character   Class :character   Class :character  
 Median :2013-09-08 00:00:00   Mode  :character   Mode  :character   Mode  :character  
 Mean   :2013-12-03 12:13:46                                                           
 3rd Qu.:2015-02-28 00:00:00                                                           
 Max.   :2016-12-05 00:00:00                                                           
 NA's   :183                                                                           
  LOT_NUMBER        SUBDIVISION        CASE_TYPE         CASE_DESCRIPTION
 Length:7133        Length:7133        BLD:7133   Building Permit:7133   
 Class :character   Class :character                                     
 Mode  :character   Mode  :character                                     
                                                                         
                                                                         
                                                                         
                                                                         
                  LEGAL_USE    ESTIMATED_COST    PARCEL_SIZE      PARCEL_CLUSTER_SECTOR
 ONE FAMILY DWELLING   :2709   Min.   :     0   Min.   :      0   Min.   : 1.000       
 RES                   : 920   1st Qu.:  4525   1st Qu.:   3354   1st Qu.: 2.000       
 ONE FAMILY            : 798   Median : 13470   Median :   4095   Median : 5.000       
 TWO FAMILY DWELLING   : 479   Mean   : 18955   Mean   :  13540   Mean   : 4.656       
 SINGLE FAMILY DWELLING: 358   3rd Qu.: 18750   3rd Qu.:   4932   3rd Qu.: 7.000       
 (Other)               :1425   Max.   :448200   Max.   :2554576   Max.   :10.000       
 NA's                  : 444   NA's   :6791     NA's   :45        NA's   :11           
    STORIES       PARCEL_FLOOR_AREA PARCEL_GROUND_AREA PRC_AKA_ADDRESS     BLD_PERMIT_TYPE
 Min.   : 1.000   Min.   :      0   Min.   :    0.0    Length:7133        DISM     :5859  
 1st Qu.: 1.000   1st Qu.:      0   1st Qu.:  630.0    Class :character   Dismantle:1274  
 Median : 1.500   Median :      0   Median :  768.0    Mode  :character                   
 Mean   : 2.126   Mean   :   6830   Mean   :  742.3                                       
 3rd Qu.: 2.000   3rd Qu.:      0   3rd Qu.:  912.0                                       
 Max.   :26.000   Max.   :5102782   Max.   :27316.0                                       
 NA's   :1426     NA's   :45        NA's   :45                                            
 PERMIT_DESCRIPTION                BLD_PERMIT_DESC              BLD_TYPE_USE 
 Dismantle:5859     WRECK AND REMOVE DEBRIS:3077   28                 :4265  
 NA's     :1274     WRECK & REMOVE DEBRIS  :2060   One Family Dwelling:1121  
                    WRECK & REMOVE DEBRIS. : 469   54                 : 944  
                    Wrecking Permit        : 292   6                  : 260  
                    WRECK AND REMOVE       :  31   26                 :  92  
                    (Other)                : 213   (Other)            : 399  
                    NA's                   : 991   NA's               :  52  
          RESIDENTIAL                DESCRIPTION   BLD_TYPE_CONST_COD BLD_ZONING_DIST
 NON-RESIDENTIAL: 659   One Family Dwelling:4265   5B     :6456       R1     :1350   
 RESIDENTIAL    :6474   Two Family Dwelling: 944   3B     : 323       R2     : 790   
                        Commercial Building: 260   2B     :  61       B4     :  96   
                        Multiple Dwelling  :  92   1B     :  35       R3     :  44   
                        School             :  42   5A     :  23       M4     :  29   
                        (Other)            : 256   (Other):  34       (Other): 104   
                        NA's               :1274   NA's   : 201       NA's   :4720   
 BLD_USE_GROUP  BLD_BASEMENT    FEE_TYPE     CSM_CASENO        CSF_CREATED_BY     SEQ_NO 
 R3     :6216   N   :1051    WPMT   :7037   Length:7133        L-FW   :1636   Min.   :1  
 M      :  98   Y   :5226    BCP    :  27   Class :character   RSA    :1435   1st Qu.:1  
 R2     :  72   NA's: 856    BPM5   :  12   Mode  :character   GRE    :1105   Median :1  
 B      :  60                REIN   :   9                      H-AP   : 641   Mean   :1  
 E      :  53                SPEC   :   9                      M-JD   : 488   3rd Qu.:1  
 (Other): 189                WPM2   :   9                      M-LR   : 444   Max.   :1  
 NA's   : 445                (Other):  30                      (Other):1384              
   PCF_AMT_PD       PCF_AMT_DUE       PCF_UPDATED        OWNER_LAST_NAME   
 Min.   :    0.0   Min.   :    0.0   Min.   :     1313   Length:7133       
 1st Qu.:  108.0   1st Qu.:  238.0   1st Qu.:     8615   Class :character  
 Median :  238.0   Median :  238.0   Median :    41211   Mode  :character  
 Mean   :  257.3   Mean   :  274.1   Mean   :    90674                     
 3rd Qu.:  238.0   3rd Qu.:  238.0   3rd Qu.:    72085                     
 Max.   :20494.2   Max.   :20494.2   Max.   :101112414                     
 NA's   :1274                        NA's   :471                           
 OWNER_FIRST_NAME   OWNER_ADDRESS1     OWNER_ADDRESS2      OWNER_CITY       
 Length:7133        Length:7133        Length:7133        Length:7133       
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
                                                                            
 OWNER_STATE         OWNER_ZIP         CONTRACTOR_LAST_NAME CONTRACTOR_FIRST_NAME
 Length:7133        Length:7133        Length:7133          Length:7133          
 Class :character   Class :character   Class :character     Class :character     
 Mode  :character   Mode  :character   Mode  :character     Mode  :character     
                                                                                 
                                                                                 
                                                                                 
                                                                                 
 CONTRACTOR_ADDRESS1 CONTRACTOR_ADDRESS2 CONTRACTOR_CITY    CONTRACTOR_STATE  
 Length:7133         Length:7133         Length:7133        Length:7133       
 Class :character    Class :character    Class :character   Class :character  
 Mode  :character    Mode  :character    Mode  :character   Mode  :character  
                                                                              
                                                                              
                                                                              
                                                                              
 CONTRACTOR_ZIP 
 Min.   :48009  
 1st Qu.:48204  
 Median :48209  
 Mean   :48230  
 3rd Qu.:48227  
 Max.   :92606  
 NA's   :179    
                                                                                                                                                                                                                                                                           CONDITION_FOR_APPROVAL
 Call for inspection upon compliance.Ord. 290-H, 12-11-14.3, ET SEQ.CC: OWNER: Borman LLC.                     30600 Telegraph Rd.                     Bingham Farms, MI 48025CC: TENANT: LD Acquistion Co.                     P.O. Box 3429                     El Segunda, CA 90245:   1      
 Remove all foundations from site and backfill.Ord. 290-H, 12-11-19.9, ET SEQ.                                                                                                                                                                                                        :   1      
 NA's                                                                                                                                                                                                                                                                                 :7131      
                                                                                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                                                                                 
 site_location      owner_location     contractor_location     geom          
 Length:7133        Length:7133        Length:7133         Length:7133       
 Class :character   Class :character   Class :character    Class :character  
 Mode  :character   Mode  :character   Mode  :character    Mode  :character  
                                                                             
                                                                             
                                                                             
                                                                             

Extracting building lat longs from “site_location” variable

data_permits %<>%
  #filter out permits that have no lat/long
  filter(grepl("\\([0-9\\.\\-]+, *[0-9\\.\\-]+\\)",site_location)) %>%
  #extracting lat longs
  mutate(lat = as.double(sub(".*\\(([0-9\\.\\-]+),.*","\\1", site_location))) %>%
  mutate(long = as.double(sub(".*, *([0-9\\.\\-]+).*","\\1", site_location))) %>%
  mutate(address_only = sub("([^\\(]+)\\([0-9\\.\\-]+,.*","\\1", site_location))

Create a list of buildings doing some magic to remove those entries whose lat/long stdev is larger than 10e-4 (~11m). Not much gets removed actually, but it’s consistent with steps below. Removed those records whose address was missing (only ~40).

bld_list_permit <- data_permits %>%
  mutate(r = sqrt(PARCEL_SIZE/pi) ) %>%
  select(address=address_only, PARCEL_NO, LOT_NUMBER, PERMIT_ISSUED, PARCEL_SIZE, lat, long, r) %>%
  filter(! grepl("\\([0-9\\.\\-]+, *[0-9\\.\\-]+\\)",address)) %>%
  arrange(address, desc(PERMIT_ISSUED)) %>%
  group_by(address) %>%
  mutate(sdlat=sd(lat), sdlong=sd(long)) %>%
  filter((sdlat<10e-4 & sdlong<10e-4) | (is.na(sdlat) | is.na(sdlong))) %>%
  filter(long > -83.3 & long < -82.8) %>%
  filter(lat > 42.2 & lat < 42.5) %>%
  arrange(PERMIT_ISSUED) %>%
  summarise(n_permits=n(), last_permit=last(PERMIT_ISSUED), 
            lat=median(lat), long=median(long), r=last(r))
head(bld_list_permit)

Function to return number of bligthed records for a specific lat/long coordinate. This function is used to assign blight computing the distance directly in degrees 0.0001 ~ 11m ~ 37ft, which is faster than using a built in function such as distGeo.

in_blight <- function(lt, ln, data) {
  data %>% 
    filter(sqrt((lat-lt)^2+(long-ln)^2)<rdegr+0.0001) %>%
    nrow()
}
#creating the dataframe of bligthed buildings
indata <- bld_list_permit %>% 
  filter(!is.na(r)) %>%
  unique() %>%
  select(address, lat, long, r)
indata_degrees <- indata %>%
  mutate(rdegr = 0.0001/37 * r)

Loading blight violation incidents

head(data_violations)

Data Violation exploration, converting first to the right data fields

#converting to factors
cols <- c("AgencyName","ViolationCode","Disposition","PaymentStatus","Void",
          "ViolationCategory","Country")
data_violations %<>% mutate_each_(funs(factor(.)),cols)
#converting $$ to numeric
cols <- c("FineAmt","AdminFee","LateFee","StateFee","CleanUpCost","JudgmentAmt")
data_violations %<>% mutate_each_(funs(from_currency(.)),cols)
#not converting to dates as the dates in the fields below have weird years
cols <-c("TicketIssuedDT","HearingDT")
#data_violations %<>% mutate_each_(funs(from_currency(.)),cols)
summary(data_violations)
    TicketID      TicketNumber                                            AgencyName    
 Min.   : 18645   Length:307804      Building and Safety Engineering Department:173311  
 1st Qu.:101806   Class :character   Department of Public Works                :112757  
 Median :183824   Mode  :character   Detroit Police Department                 : 12853  
 Mean   :182967                      Health Department                         :  8881  
 3rd Qu.:265211                      Neighborhood City Halls                   :     2  
 Max.   :339184                                                                         
                                                                                        
   ViolName         ViolationStreetNumber ViolationStreetName MailingStreetNumber
 Length:307804      Min.   :   -11064     Length:307804       Min.   :        0  
 Class :character   1st Qu.:     4936     Class :character    1st Qu.:     3107  
 Mode  :character   Median :    10624     Mode  :character    Median :     9359  
                    Mean   :    12001                         Mean   :    18527  
                    3rd Qu.:    15895                         3rd Qu.:    18251  
                    Max.   :222222222                         Max.   :222222222  
                                                              NA's   :808        
 MailingStreetName  MailingCity        MailingState       MailingZipCode    
 Length:307804      Length:307804      Length:307804      Length:307804     
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
                                                                            
 NonUsAddressCode             Country       TicketIssuedDT     TicketIssuedTime 
 Length:307804      US            : 18062   Length:307804      Length:307804    
 Class :character   United Kingdom:    97   Class :character   Class1:hms       
 Mode  :character   Singapore     :    96   Mode  :character   Class2:difftime  
                    Canada        :    71                      Mode  :numeric   
                    Australia     :    22                                       
                    (Other)       :   136                                       
                    NA's          :289320                                       
  HearingDT          CourtTime           ViolationCode    ViolDescription   
 Length:307804      Length:307804     9-1-36(a) :106521   Length:307804     
 Class :character   Class1:hms        9-1-81(a) : 45036   Class :character  
 Mode  :character   Class2:difftime   9-1-104   : 38612   Mode  :character  
                    Mode  :numeric    22-2-88   : 28730                     
                                      22-2-88(b): 21804                     
                                      9-1-110(a):  6779                     
                                      (Other)   : 60322                     
                            Disposition        FineAmt           AdminFee     LateFee      
 Responsible By Default           :172128   Min.   :    0.0   Min.   :20   Min.   :   0.0  
 Not responsible By Dismissal     : 53543   1st Qu.:  125.0   1st Qu.:20   1st Qu.:  10.0  
 Not responsible By City Dismissal: 44380   Median :  250.0   Median :20   Median :  25.0  
 Responsible By Admission         : 16432   Mean   :  360.2   Mean   :20   Mean   :  35.8  
 Responsible By Determination     : 10233   3rd Qu.:  250.0   3rd Qu.:20   3rd Qu.:  25.0  
 Not responsible By Determination :  7809   Max.   :10000.0   Max.   :20   Max.   :1000.0  
 (Other)                          :  3279   NA's   :1973                                   
    StateFee   CleanUpCost         JudgmentAmt                   PaymentStatus   
 Min.   :10   Min.   :    0.000   Min.   :    0.0   NO PAYMENT APPLIED  :244303  
 1st Qu.:10   1st Qu.:    0.000   1st Qu.:  140.0   NO PAYMENT ON RECORD: 14565  
 Median :10   Median :    0.000   Median :  305.0   PAID IN FULL        : 44319  
 Mean   :10   Mean   :    0.515   Mean   :  425.2   PARTIAL PAYMENT MADE:  4617  
 3rd Qu.:10   3rd Qu.:    0.000   3rd Qu.:  305.0                                
 Max.   :10   Max.   :13123.800   Max.   :11030.0                                
                                  NA's   :1972                                   
   Void        ViolationCategory ViolationAddress   MailingAddress    
 0   : 99133   0:305787          Length:307804      Length:307804     
 NA's:208671   1:  2017          Class :character   Class :character  
                                 Mode  :character   Mode  :character  
                                                                      
                                                                      
                                                                      
                                                                      

Getting the violation codes, we’ll manually categorize them below

violCodes <- data_violations %>% 
  select(ViolationCode, ViolDescription) %>%
  unique()

Generated lat/longs and address, and cleaned data by keeping records with Detroit addresses only. Didn’t filter by country as it removed most of the entries (from 300k to 13k records). Didn’t remove disposition “not reposonsible” or “pending” as this could contain information

viol_list <- data_violations %>% 
#  filter(Country == "US") %>%
  filter(grepl("\\([0-9\\.\\-]+, *[0-9\\.\\-]+\\)",ViolationAddress)) %>%
  #extracting lat longs
  mutate(lat = as.double(sub(".*\\(([0-9\\.\\-]+),.*","\\1", ViolationAddress))) %>%
  mutate(long = as.double(sub(".*, *([0-9\\.\\-]+).*","\\1", ViolationAddress))) %>%
  mutate(address_only = sub("([^\\(]+)\\([0-9\\.\\-]+,.*","\\1", ViolationAddress)) %>%
  filter(grepl("Detroit",ViolationAddress)) %>%
#  filter(! grepl("Not responsible",Disposition)) %>%
#  filter(! grepl("PENDING", Disposition)) %>%
  select(lat, long, ViolationCode, Disposition, JudgmentAmt, PaymentStatus, ViolationCategory, address_only) 
head(viol_list)

What happens with Disposition? Well, it seems hat it could be categorized as responsible, not responsible, and pending

viol_list %>%
  select(Disposition) %>%
  group_by(Disposition) %>%
  summarise(n())

There are about 80k unique lat/longs, some of them generate a huge amount of violations, here are the top 30 lat/longs

top30viols <- viol_list %>%
  select(lat, long) %>%
  mutate(geocord = paste(lat,long)) %>%
  group_by(geocord) %>%
  summarize(lat=last(lat), long=last(long), num_viols_in_geo = n()) %>%
  arrange(desc(num_viols_in_geo)) %>%
  head(30)
top30viols

Plotting them, we see that the top one has 21k violations and it’s in the center of Detroit, probably a standard lat/long coordinate when not the actual is not available. I’m not sure about the others with over 1000 violations…

p <- gmap(lat = 42.37, lng = -83.10, zoom = 11, width = 600, height = 350,
          map_style = gmap_style("apple_mapsesque")) %>%
  ly_points(long, lat, data = top30viols, hover = num_viols_in_geo, 
            col = 'red', alpha = pmin(num_viols_in_geo / 1000, 1)) %>%
  x_axis(visible = FALSE) %>%
  y_axis(visible = FALSE)
p

Are there the same number of unique addresses?

viol_list %>%
  select(address_only) %>%
  group_by(address_only) %>%
  summarize(num_viols_in_address = n()) %>%
  arrange(desc(num_viols_in_address))

Well, it seems that there are about 110k unique addresses, and 73k unique lat/longs. Let’s see how they contrast in terms of number of violations (lat/long vs. address)

viol_list_cleaned <- viol_list %>%
  mutate(geocoord = paste(lat,long)) %>%
  group_by(geocoord) %>%
  mutate(num_viols_in_geocoord = n())
viol_list_cleaned %<>% 
  group_by(address_only) %>%
  mutate(num_viols_in_address = n())
nrow(viol_list_cleaned)
[1] 307804

It seems that about 70k entries have different number of violations when looking by address or lat/long, being 35k unique records duplicated, so getting rid of them

viol_list_cleaned %<>%
  filter(! num_viols_in_address != num_viols_in_geocoord) %>%
  group_by(ViolationCode, address_only) %>%
  mutate(num_viols_by_vcode = n()) %>%
  arrange(desc(num_viols_in_geocoord)) %>%
  ungroup() %>%
  unique() 
nrow(viol_list_cleaned)
[1] 220116

After cleaning, here are the top violations counts per geocoord/address

viol_list_cleaned %>%
  select(address_only, lat, long) %>%
  mutate(geocord = paste(lat,long)) %>%
  group_by(geocord) %>%
  summarize(address=last(address_only), lat=last(lat), long=last(long), num_viols_in_geo = n()) %>%
  arrange(desc(num_viols_in_geo)) %>%
  select(-geocord) %>%
  head(30)

There are 313 violation codes, some of them being more frequent than others

violCodes <- viol_list_cleaned %>% 
#  mutate(ViolationCode = sub("^([0-9]+-[0-9]+)-.*$","\\1",ViolationCode)) %>%
  group_by(ViolationCode) %>%
  tally(sort=TRUE) 
violCodes

Categorizing them semantically manually, we reduce them to 12 groups, n being the number of categories mapped to each corresponding group

violCodes_manual_categorization <-
  read_csv("violCodes_manual_categorization.csv")
Parsed with column specification:
cols(
  ViolGroup = col_character(),
  ViolationCode = col_character(),
  ViolDescription = col_character()
)
violCodes_manual_categorization %<>%
  mutate(ViolGroup=as.factor(ViolGroup),
         ViolationCode=as.factor(ViolationCode))
violCodes_manual_categorization %>% group_by(ViolGroup) %>% tally(sort = TRUE)

We adding the grouping factor for violation categories, ViolGroup

viol_list_cleaned %<>% 
  left_join(violCodes_manual_categorization,by="ViolationCode")
joining factors with different levels, coercing to character vector
#Some codes had no description, to prevent to NAs, grouping them to other
viol_list_cleaned[which(is.na(viol_list_cleaned$ViolGroup)),]$ViolGroup <- "other"
head(viol_list_cleaned)

Expand ViolGroup counts as separate features

violcodes_counts <- viol_list_cleaned %>%
  select(address=address_only, lat, long, ViolGroup, Disposition, 
         JudgmentAmt, PaymentStatus) %>%
  group_by(address, ViolGroup) %>%
  summarize(num_viol_by_code = n()) %>%
  ungroup() %>%
  spread(ViolGroup, num_viol_by_code, fill = 0)
head(violcodes_counts)

Getting a list of buildings, the grouping wouldn’t be necessary as there is a one to one lat/long to address correspondance, but leaving it just in case.

bld_list_viol <- viol_list_cleaned %>%
  select(address=address_only, lat, long, ViolationCode, Disposition, 
         JudgmentAmt, PaymentStatus) %>%
  filter(long > -83.3 & long < -82.8) %>%
  filter(lat > 42.2 & lat < 42.5) %>%
  mutate(Disposition = ifelse(grepl("^Responsible", Disposition),"Responsible","Not Responsible or Pending")) %>%
  group_by(address) %>%
  mutate(sdlat=sd(lat), sdlong=sd(long)) %>%
  filter((sdlat<10e-4 & sdlong<10e-4) | (is.na(sdlat) | is.na(sdlong))) %>%
  group_by(address, Disposition) %>%
  mutate(num_disposition = n()) %>% 
  group_by(address) %>%
  mutate(num_viols = n(), max_amt = max(JudgmentAmt)) %>%
  filter(Disposition == "Responsible") %>%
  summarise(lat=median(lat), long=median(long), num_viols = last(num_viols), 
            num_responsible = last(num_disposition), max_amt =last(max_amt)) %>%
  unique() 
head(bld_list_viol)

Adding the violation code counts

bld_list_viol %<>%
  left_join(violcodes_counts, by="address")
colnames(bld_list_viol) <- make.names(colnames(bld_list_viol))
head(bld_list_viol)

Exploring 311 data

#converting to factors
cols <- c("issue_type", "ticket_status")
data_311 %<>% mutate_each_(funs(factor(.)),cols)
#converting to dates
cols <-c("ticket_closed_date_time", "acknowledged_at", "ticket_created_date_time",
         "ticket_last_updated_date_time")
data_311 %<>% mutate_each_(funs(parse_date_time(.,orders="mdY HMS Op",tz="America/Detroit")),cols)
#dplyr::glimpse(data_311)
summary(data_311)
   ticket_id           city                                          issue_type  
 Min.   :1184398   Length:19680       Illegal Dumping / Illegal Dump Sites:3584  
 1st Qu.:1591936   Class :character   Tree Issue                          :3546  
 Median :1705228   Mode  :character   Running Water in a Home or Building :2655  
 Mean   :1699224                      Clogged Drain                       :2490  
 3rd Qu.:1838305                      Potholes                            :2399  
 Max.   :1975499                      Traffic Sign Issue                  :1030  
                                      (Other)                             :3976  
      ticket_status  issue_description      rating       ticket_closed_date_time      
 Acknowledged:3012   Length:19680       Min.   : 1.000   Min.   :2014-07-14 16:24:49  
 Archived    :9600   Class :character   1st Qu.: 2.000   1st Qu.:2015-04-14 13:14:10  
 Closed      :7009   Mode  :character   Median : 3.000   Median :2015-06-15 13:52:22  
 Open        :  59                      Mean   : 2.693   Mean   :2015-06-01 22:16:27  
                                        3rd Qu.: 3.000   3rd Qu.:2015-08-13 15:31:53  
                                        Max.   :19.000   Max.   :2015-10-15 02:54:02  
                                                         NA's   :3175                 
 acknowledged_at               ticket_created_date_time      ticket_last_updated_date_time
 Min.   :2014-07-14 14:05:07   Min.   :2014-07-14 12:54:47   Min.   :2014-07-14 17:06:47  
 1st Qu.:2015-04-16 12:46:43   1st Qu.:2015-04-14 15:37:22   1st Qu.:2015-04-21 09:06:55  
 Median :2015-06-15 12:58:42   Median :2015-06-10 13:55:45   Median :2015-06-22 11:44:41  
 Mean   :2015-06-05 01:45:13   Mean   :2015-06-01 17:55:42   Mean   :2015-06-10 10:54:26  
 3rd Qu.:2015-08-11 13:51:43   3rd Qu.:2015-08-10 12:07:14   3rd Qu.:2015-08-21 09:12:26  
 Max.   :2015-10-14 20:43:23   Max.   :2015-10-15 00:32:16   Max.   :2015-10-15 02:54:02  
 NA's   :2023                                                                             
   address               lat             lng           location            image          
 Length:19680       Min.   :41.88   Min.   :-86.55   Length:19680       Length:19680      
 Class :character   1st Qu.:42.36   1st Qu.:-83.19   Class :character   Class :character  
 Mode  :character   Median :42.39   Median :-83.11   Mode  :character   Mode  :character  
                    Mean   :42.39   Mean   :-83.11                                        
                    3rd Qu.:42.42   3rd Qu.:-83.04                                        
                    Max.   :42.45   Max.   :-82.91                                        
                                                                                          

There are 23 types of 311 issues, we could extract counts for each

types311 <- data_311 %>%
  group_by(issue_type) %>%
  summarise(num_311type = n()) %>%
  arrange(desc(num_311type))
types311

Generating features for each issue type as a count per address

type311counts <- data_311 %>%
  select(address, issue_type) %>%
  group_by(address, issue_type) %>%
  summarize(num_type311 = n()) %>%
  ungroup() %>%
  spread(issue_type, num_type311, fill = 0) 
head(type311counts)

Most tickets are either archived or closed, probably there is no discrimination around that

data_311 %>%
  group_by(ticket_status) %>%
  summarise(num_tx_status = n()) %>%
  arrange(desc(num_tx_status))

Rating could be interesting to use

data_311 %>%
  group_by(rating) %>%
  summarise(num_rating = n()) %>%
  arrange(desc(num_rating))

All entries are for city of Detroit, which is good, but won’t be discriminating

data_311 %>%
  group_by(city) %>%
  summarise(num_city = n()) %>%
  arrange(desc(num_city))

Grouping by address, it seems there are multiple lat/longs per address, removing those entries that have a lat/long STDEV larger than 10e-4 (~11m), removes 62 entries.

bld_list_311 <- data_311 %>%
  select(address, lat, long=lng, rating, type311 = issue_type, rating) %>%
  filter(long > -83.3 & long < -82.8) %>%
  filter(lat > 42.2 & lat < 42.5) %>%
  group_by(address) %>%
  mutate(sdlat=sd(lat), sdlong=sd(long)) %>%
  filter((sdlat<10e-4 & sdlong<10e-4) | (is.na(sdlat) | is.na(sdlong))) %>%
  summarize(lat=median(lat), long=median(long), num_311 = n(), max_rating = max(rating), min_rating=min(rating), diff_rating = max(rating) - min(rating)) %>%
  unique()
head(bld_list_311)

Most 311 entries don’t change the rating

bld_list_311 %>%
  group_by(diff_rating) %>%
  tally(sort=TRUE)

Adding the 311 issue type counts

bld_list_311 %<>%
  left_join(type311counts, by="address")
colnames(bld_list_311) <- make.names(colnames(bld_list_311))
head(bld_list_311)

Exploring criminal incidents in Detroit

head(data_crime)
#converting to factors
cols <- c("CATEGORY","STATEOFFENSEFILECLASS","PRECINCT","COUNCIL","NEIGHBORHOOD")
data_crime %<>% mutate_each_(funs(factor(.)),cols)
#converting to dates
cols <-c("INCIDENTDATE")
data_crime %<>% mutate_each_(funs(parse_date_time(.,orders="mdY HMS Op",tz="America/Detroit")),cols)
#dplyr::glimpse(data_crime)
summary(data_crime)
     ROWNUM           CASEID            INCINO         
 Min.   :     1   Min.   :1612272   Min.   :0.000e+00  
 1st Qu.: 29984   1st Qu.:1930308   1st Qu.:1.504e+09  
 Median : 59966   Median :1960745   Median :1.506e+09  
 Mean   : 59966   Mean   :1960655   Mean   :1.506e+09  
 3rd Qu.: 89948   3rd Qu.:1991078   3rd Qu.:1.509e+09  
 Max.   :119931   Max.   :2021621   Max.   :1.509e+10  
                                    NA's   :2          
                                     CATEGORY     OFFENSEDESCRIPTION STATEOFFENSEFILECLASS
 TRAFFIC VIOLATIONS-MOTORCYCLE VIOLATIONS:29412   Length:119931      99009  :17383        
 ASSAULT                                 :16471   Class :character   13001  :11140        
 LARCENY                                 :14138   Mode  :character   29000  : 9418        
 DAMAGE TO PROPERTY                      : 9418                      13002  : 8283        
 AGGRAVATED ASSAULT                      : 8283                      24001  : 6961        
 BURGLARY                                : 7764                      (Other):66738        
 (Other)                                 :34445                      NA's   :    8        
  INCIDENTDATE                      HOUR            SCA            PRECINCT    
 Min.   :2015-01-01 00:00:00   Min.   : 0.00   Min.   : 201.0   8      :14076  
 1st Qu.:2015-03-31 00:00:00   1st Qu.: 8.00   1st Qu.: 409.0   3      :13913  
 Median :2015-06-16 00:00:00   Median :14.00   Median : 711.0   9      :13318  
 Mean   :2015-06-14 10:37:57   Mean   :12.91   Mean   : 765.1   12     :12874  
 3rd Qu.:2015-08-30 00:00:00   3rd Qu.:18.00   3rd Qu.:1002.0   6      :11940  
 Max.   :2015-12-10 00:00:00   Max.   :23.00   Max.   :9999.0   (Other):53039  
                                               NA's   :771      NA's   :  771  
                    COUNCIL                NEIGHBORHOOD    CENSUSTRACT      
 City Council District 6:18330   GREENFIELD      : 3944   Min.   :       9  
 City Council District 7:18212   STATE FAIR-NOLAN: 3942   1st Qu.:    5114  
 City Council District 2:16619   WARRENDALE      : 3582   Median :    5263  
 City Council District 3:16287   DENBY           : 2955   Mean   :   37989  
 City Council District 5:16230   PEMBROKE        : 2888   3rd Qu.:    5393  
 (Other)                :30131   (Other)         :97961   Max.   :99992016  
 NA's                   : 4122   NA's            : 4659   NA's   :13064     
   ADDRESS               LON                LAT             LOCATION        
 Length:119931      Min.   :  -121.0   Min.   :     0.0   Length:119931     
 Class :character   1st Qu.:   -83.2   1st Qu.:    42.4   Class :character  
 Mode  :character   Median :   -83.1   Median :    42.4   Mode  :character  
                    Mean   :  2236.2   Mean   :  2361.4                     
                    3rd Qu.:   -83.0   3rd Qu.:    42.4                     
                    Max.   :999999.0   Max.   :999999.0                     
                    NA's   :59         NA's   :59                           

There are 50 categories of crimes, could be added as counts to the features

crime_categories <- data_crime %>%
  group_by(CATEGORY) %>%
  tally(sort=TRUE)
crime_categories

Seems that crime is spread evenly, except for districts 3 and 5

data_crime %>% 
  group_by(COUNCIL) %>%
  tally(sort=TRUE)

Grouping by address, it seems there are multiple lat/longs per address, and about 1000 entries have a lat/long STDEV larger than 10e-4 (~11m distance), so I removed those

bld_list_crime <- data_crime %>%
  select(address=ADDRESS, lat=LAT, long=LON, typecrime = CATEGORY) %>%
  filter(long > -83.3 & long < -82.8) %>%
  filter(lat > 42.2 & lat < 42.5) %>%
  group_by(address) %>%
  mutate(sdlat=sd(lat), sdlong=sd(long)) %>%
  filter((sdlat<10e-4 & sdlong<10e-4) | (is.na(sdlat) | is.na(sdlong))) %>%
  summarize(lat=median(lat), long=median(long), num_crime = n()) %>%
  unique()
head(bld_list_crime)

Generating crime features for each issue type as a count per address

crime_category_counts <- data_crime %>%
  select(address=ADDRESS, CATEGORY) %>%
  group_by(address, CATEGORY) %>%
  summarize(num_category = n()) %>%
  ungroup() %>%
  spread(CATEGORY, num_category, fill = 0) 
head(crime_category_counts)

Adding the crime category counts

bld_list_crime %<>%
  left_join(crime_category_counts, by="address")
colnames(bld_list_crime) <- make.names(colnames(bld_list_crime))
head(bld_list_crime)

Prepare a list of coordinates by type of record, at precision of 4 digits (~11m accuracy) REF: https://en.wikipedia.org/wiki/Decimal_degrees#Precision

bld_list_coord <- bld_list_311 %>%
  mutate(type = "311") %>%
  select(type, lat, lon=long, address) %>%
  bind_rows(select(mutate(bld_list_crime, type="crime"), type, lat, lon=long, address)) %>%
  bind_rows(select(mutate(bld_list_viol, type="viol"), type, lat, lon=long, address)) %>%
  mutate(coord=paste(lat,lon,sep=",")) %>%
  arrange(desc(lat), desc(lon))
  
bld_list_coord

Let’s plot all records of 311 calls, blight violations, and crime reports in a map centered in Detroit. The records are grouped by distance. When zooming in, we see that lat/long coordinates that are about +/-0.0002 degrees appart seem to belong to the same location

library(leaflet)
iconurl <- "https://docs.google.com/drawings/d/11vcxQDH5DQstHFuUf0VhoXoWFx5b8elfZseXPLcbsmE/pub?w=50&h=50"
leaflet() %>% 
  setView(lat = 42.37, lng = -83.10, zoom = 11) %>% 
  addTiles(group = "OSM (default)") %>%
  addMarkers(data = filter(bld_list_coord,type=="311"), lng = ~lon, lat = ~lat,
             clusterOptions = markerClusterOptions(), group = "311 calls",
             popup = ~coord, label = ~type) %>%
  addMarkers(data = filter(bld_list_coord,type=="crime"), lng = ~lon, lat = ~lat,
             clusterOptions = markerClusterOptions(), group = "crime",
             popup = ~coord, label = ~type) %>%
  addMarkers(data = filter(bld_list_coord,type=="viol"), lng = ~lon, lat = ~lat,
             clusterOptions = markerClusterOptions(), group = "blight viol",
             popup = ~coord, label = ~type) %>%
  addMarkers(data = bld_list_permit, lng = ~long, lat = ~lat, 
             clusterOptions = markerClusterOptions(), group = "demolition", 
             popup = ~address, label = "demolition",
             icon = list(iconUrl = iconurl, iconsize = c(1,1))) %>%
  addLayersControl(
    overlayGroups = c("311 calls", "crime", "blight viol","demolition"),
    options = layersControlOptions(collapsed = FALSE)
  )

Let’s build a list of potential locations to explore by grouping all lat/long coordinates that are around +/-0.0002 degrees appart. This approach yields to 60679 buildings out of 146892 records.

# Allowable error
e_lat = 0.0002
e_lon = 0.0002
bld_list_all_filename <- "extracted_bld_list_elat2-elon2.csv"
# Calculation takes ~30 min, skip it if file above exists to speed up
if( file.exists(bld_list_all_filename) ) {
  bld_list_all <- read_csv(bld_list_all_filename)
# file doesn't exists, let's crank the calculation up!
} else {
  i <- 0
  out <- data_frame()
  
  input <- bld_list_coord %>%
    mutate(coord_assigned = 0)
  
  l <- nrow(input)
  
  while(l > 0) {
    input %<>% 
      filter(coord_assigned != 1)
    
    lt <- input[1,]$lat
    ln <- input[1,]$lon
    
    input %<>%
      mutate(coord_assigned = ifelse( (abs(lon-ln)<=e_lon & abs(lat-lt)<=e_lat), 1, 0) )
    
    i <- i+1
    
    out <- input %>% 
      filter(coord_assigned == 1) %>%
      mutate(bld_id = i, bld_lat = median(lat), bld_lon = median(lon)) %>%
      bind_rows(out)
    
    l <- nrow(input)
    if(l == 0) break
    if(i%%100==0) print(paste0(i," iterations ",l," records left"))
  } 
  bld_list_all <- out
  out <- NULL
  write_csv(bld_list_all, path = bld_list_all_filename)
}
Parsed with column specification:
cols(
  type = col_character(),
  lat = col_double(),
  lon = col_double(),
  address = col_character(),
  coord = col_character(),
  coord_assigned = col_double(),
  bld_id = col_double(),
  bld_lat = col_double(),
  bld_lon = col_double()
)

Let’s join in the building identifier to the existing records

bld_list_crime <- bld_list_all %>% 
  filter(type == "crime") %>%
  select(bld_id, bld_lat, bld_lon, address) %>%
  left_join(bld_list_crime, by="address") %>% 
  select(-lat, -long) %>%
  rename(lat=bld_lat, long=bld_lon)
bld_list_311 <- bld_list_all %>% 
  filter(type == "311") %>%
  select(bld_id, bld_lat, bld_lon, address) %>%
  left_join(bld_list_311, by="address") %>%
  select(-lat, -long) %>%
  rename(lat=bld_lat, long=bld_lon)
bld_list_viol <- bld_list_all %>% 
  filter(type == "viol") %>%
  select(bld_id, bld_lat, bld_lon, address) %>%
  left_join(bld_list_viol, by="address") %>%
  select(-lat, -long) %>%
  rename(lat=bld_lat, long=bld_lon)

Recalculate summaries at the building level for crime

tmp <- bld_list_crime %>%
  select(-address) %>%
  select(-lat, -long) %>%
  group_by(bld_id) %>% 
  summarise_each(funs(sum))
bld_list_crime %<>% 
  select(bld_id, lat, long) %>% 
  distinct() %>%
  left_join(tmp, by="bld_id")
head(bld_list_crime)

Recalculate summaries at the building level for 311

tmp <- bld_list_311 %>%
  select(-address) %>%
  select(-lat, -long, -min_rating, -max_rating, -diff_rating) %>%
  group_by(bld_id) %>% 
  summarise_each(funs(sum))
tmp <- bld_list_311 %>% 
  select(bld_id, min_rating, max_rating) %>%
  group_by(bld_id) %>% 
  summarise(min_rating = min(min_rating), max_rating = max(max_rating)) %>%
  mutate(diff_rating = max_rating - min_rating) %>%
  left_join(tmp, by="bld_id")
bld_list_311 %<>% 
  select(bld_id, lat, long) %>% 
  distinct() %>%
  left_join(tmp, by="bld_id")
head(bld_list_311)

Recalculate summaries at the building level for blight violations

tmp <- bld_list_viol %>%
  select(-address) %>%
  select(-lat, -long, -max_amt) %>%
  group_by(bld_id) %>% 
  summarise_each(funs(sum))
tmp <- bld_list_viol %>% 
  select(bld_id, max_amt) %>%
  group_by(bld_id) %>% 
  summarise(max_amt = max(max_amt)) %>%
  left_join(tmp, by="bld_id")
bld_list_viol %<>% 
  select(bld_id, lat, long) %>% 
  distinct() %>%
  left_join(tmp, by="bld_id")
head(bld_list_viol)

Direct join by building identifier, bld_id

bld_list_crime_311_viol <- bld_list_crime %>%
  full_join(bld_list_311, by = "bld_id") %>%
  full_join(bld_list_viol, by = "bld_id") 
bld_list_crime_311_viol %<>% 
  mutate(lat = ifelse(is.na(lat),ifelse(is.na(lat.x),lat.y,lat.x),lat)) %>%
  mutate(long = ifelse(is.na(long),ifelse(is.na(long.x),long.y,long.x),long)) %>%
  select(-lat.x, -long.x, -lat.y, -long.y) %>%
  select(bld_id, lat, long, everything()) 
head(bld_list_crime_311_viol)

Adding surrounding statistics, 0.001 ~ 111m

bld_list_crime_311_viol %<>% 
  mutate(coord_neigh = paste(round(lat,digits=3),round(long,digits=3))) %>%
  group_by(coord_neigh) %>%
  mutate(
    num_crime_neigh = sum(num_crime),
    num_311_neigh = sum(num_311),
    avg_max_rating_neigh = mean(max_rating),
    avg_min_rating_neigh = mean(min_rating),
    num_viols_neigh = sum(num_viols),
    num_respons_neigh = sum(num_responsible),
    avg_max_amt_neigh = mean(max_amt),
    total_max_amt_neigh = sum(max_amt)
  ) %>%
  ungroup()
head(bld_list_crime_311_viol)

Taking care of NA’s, doing some dummy imputation

codesviol <- make.names(unlist(lapply(unique(violCodes_manual_categorization$ViolGroup), as.character)))
codes311 <- make.names(unlist(lapply(types311$issue_type, as.character)))
codescrime <- make.names(unlist(lapply(crime_categories$CATEGORY, as.character)))
cols <- c("num_crime", "num_311", "num_viols", "num_responsible",
          "num_crime_neigh", "num_311_neigh", "num_viols_neigh", 
          "num_respons_neigh", "total_max_amt_neigh")
cols <- c(cols, codesviol, codes311, codescrime)
bld_list_crime_311_viol %<>% 
  mutate_each_(funs(ifelse(is.na(.),0,.)),cols)
cols <- c("max_rating", "min_rating", "diff_rating", "max_amt", 
          "avg_max_rating_neigh", "avg_min_rating_neigh", "avg_max_amt_neigh")
bld_list_crime_311_viol %<>% mutate_each_(funs(ifelse(is.na(.),-1,.)),cols)
#Let's print the columns that still contain NA's, if character(0) then we are good!
out <- colnames(bld_list_crime_311_viol)[colSums(is.na(bld_list_crime_311_viol)) > 0]
ifelse(length(out)==0,print("No NA is left :)"),out)
[1] "No NA is left :)"
[1] "No NA is left :)"

The mashup results in about 4210 blighted entries, out of 82163 total buildings.

modeling_data <- bld_list_crime_311_viol 
modeling_data %<>%
  rowwise() %>%
  mutate(nblighted = in_blight(lat, long, indata_degrees)) 
modeling_data %>%
  filter(nblighted > 0) %>% 
  nrow()
[1] 4210

Creating dependent variable “condition” as boolean from “nblighted” (this describes the number of records blighted in the same lat/long)

modeling_data %<>%
  mutate(condition = factor(if_else(nblighted > 0, "BLIGHTED", "NOT_BLIGHTED"))) %>%
  select(-bld_id, -lat, -long, -nblighted, -coord_neigh)
table(modeling_data$condition)

NOT_BLIGHTED     BLIGHTED 
       77953         4210 

Balancing the two classes by downsampling the number of cases of non-blight (not ideal, but helps modeling and compute time)

mod_data_sampled <- modeling_data %>%
  filter(condition == "BLIGHTED")
mod_data_sampled <- modeling_data %>%
  filter(condition == "NOT_BLIGHTED") %>% 
  sample_n(5500) %>%
  bind_rows(mod_data_sampled)
table(mod_data_sampled$condition)

NOT_BLIGHTED     BLIGHTED 
        5500         4210 

Creating a set-aside dataset for test, and a modeling set

set.seed(107)
inTest <- createDataPartition(y=mod_data_sampled$condition, p=0.1, list=FALSE)
testSet <- mod_data_sampled[inTest,]
modelSet <- mod_data_sampled[-inTest,]
table(testSet$condition)

NOT_BLIGHTED     BLIGHTED 
         550          421 

Creating a training and validation data sets for model training and validation, the training set is about 8.4k records

set.seed(107)
inValid <- createDataPartition(y=modelSet$condition, p=0.15, list=FALSE)
trainSet <- mod_data_sampled[-inValid,]
validSet <- mod_data_sampled[inValid,]
#we are actually using the validation set as a testset, so adding them together to 
#have a better performance estimate
validSet %<>% bind_rows(testSet)
table(trainSet$condition)

NOT_BLIGHTED     BLIGHTED 
        4661         3737 

As for the validation set, it is about 1.2k records

table(validSet$condition)

NOT_BLIGHTED     BLIGHTED 
        1389          894 

Plotting the histogram of number of violations for both blighted and not blighted buildings, we see some difference that might contribute to discriminate the 2 classes

p <- figure(width = 600, height = 350) %>% 
  ly_hist(num_viols, data = trainSet[which(trainSet$condition == "NOT_BLIGHTED"),], color = "blue", alpha = 0.25, freq = F, breaks = 25) %>%
  ly_hist(num_viols, data = trainSet[which(trainSet$condition == "BLIGHTED"),], color = "red", alpha = 0.25, freq = F, breaks = 25) 
p

Let’s train a general linear model (GLM) with 5-fold cross-validation trying to predict the bligth condition only by the number of violations reported in the coordinate, which yields 72.3% AUC on the ROC, 84.2% true positive, but 37.7% true negative rates.

# 5 fold cross-validation, 1 repetition
ctrl <- trainControl(method = "repeatedcv",
                     number = 5,
                     repeats = 1,
                     classProbs = TRUE,
                     summaryFunction = twoClassSummary
                     )
# general linear model
glmModel <- train(condition ~ num_viols,
                  data = trainSet,
                  method = "glm",
                  trControl = ctrl,
                  metric = "ROC"
                  )
glmModel
Generalized Linear Model 

8398 samples
   1 predictor
   2 classes: 'NOT_BLIGHTED', 'BLIGHTED' 

No pre-processing
Resampling: Cross-Validated (5 fold, repeated 1 times) 
Summary of sample sizes: 6719, 6719, 6718, 6718, 6718 
Resampling results:

  ROC        Sens       Spec     
  0.7235278  0.8423085  0.3770386

 

A decision tree has a similar performance, 71.8% AUC on ROC for cross-validation, but at a more balanced operating point, with 65.0% true positive and 68.7% true negative.

# decision tree model
dtreeModel <- train(condition ~ num_viols,
                  data = trainSet,
                  method = "rpart",
                  trControl = ctrl,
                  metric = "ROC"
                  )
dtreeModel
CART 

8398 samples
   1 predictor
   2 classes: 'NOT_BLIGHTED', 'BLIGHTED' 

No pre-processing
Resampling: Cross-Validated (5 fold, repeated 1 times) 
Summary of sample sizes: 6718, 6719, 6717, 6719, 6719 
Resampling results across tuning parameters:

  cp            ROC        Sens       Spec     
  0.0001783962  0.7182296  0.6504980  0.6866661
  0.0092320043  0.7025044  0.5736739  0.7712712
  0.2389617340  0.5710988  0.7869099  0.3552878

ROC was used to select the optimal model using  the largest value.
The final value used for the model was cp = 0.0001783962. 

A random forest yields slighlty lower resuts, with an 71.0% AUC on ROC, but also at a balanced operating point, 65.5% true positive, and 68.0% true negative

# random forest model
rfModel <- train(condition ~ num_viols,
                  data = trainSet,
                  method = "rf",
                  trControl = ctrl,
                  metric = "ROC"
                  )
invalid mtry: reset to within valid rangeinvalid mtry: reset to within valid rangeinvalid mtry: reset to within valid rangeinvalid mtry: reset to within valid rangeinvalid mtry: reset to within valid rangeinvalid mtry: reset to within valid range
rfModel
Random Forest 

8398 samples
   1 predictor
   2 classes: 'NOT_BLIGHTED', 'BLIGHTED' 

No pre-processing
Resampling: Cross-Validated (5 fold, repeated 1 times) 
Summary of sample sizes: 6719, 6718, 6719, 6718, 6718 
Resampling results:

  ROC        Sens       Spec     
  0.7103946  0.6550085  0.6802379

Tuning parameter 'mtry' was held constant at a value of 2
 

Comparing the models across cross-validation resamples, we see a similar performance in terms of ROC. The GLM seems to achieve the highest true positive, but at a cost of a low true negative.

resamps <- resamples(list(glm = glmModel, dtree = dtreeModel, rf = rfModel))
summary(resamps)

Other variables/features:

varcrime <- "num_crime"
var311 <- paste("min_rating", "max_rating", "diff_rating","num_311",sep = "+")
varviols <- paste("max_amt","num_viols","num_responsible",sep = "+")
varneigh <- paste("num_crime_neigh", "num_311_neigh","avg_max_rating_neigh", 
                  "avg_min_rating_neigh", "num_viols_neigh", "num_respons_neigh", 
                  "avg_max_amt_neigh", "total_max_amt_neigh",sep="+")
var311codes <- paste(codes311, collapse = "+")
varcrimecodes <- paste(codescrime, collapse = "+")
varviolcodes <- paste(codesviol, collapse = "+")
f_basic <- as.formula(paste("condition ~ ", paste(varcrime, var311, varviols, sep="+")))
f_basic_neigh <- as.formula(paste("condition ~ ", paste(varcrime, var311, varviols, varneigh, sep="+")))
f_all <- as.formula(paste("condition ~ ", paste(varcrime, var311, varviols, varneigh, var311codes, varcrimecodes, varviolcodes, sep="+")))
f_all_noneigh <- as.formula(paste("condition ~ ", paste(varcrime, var311, varviols, var311codes, varcrimecodes, varviolcodes, sep="+")))

The fact that the random forest performance in training and validation sets was similar, eludes that the model in not complex enough.

Adding 311 call and crime data, yields to a slightly better AUC of 72.0% (from 71.8%)

# decision tree model
dtreeModel2 <- train(f_basic,
                  data = trainSet,
                  method = "rpart",
                  trControl = ctrl,
                  metric = "ROC",
                  na.action=na.exclude
                  )
dtreeModel2
CART 

8398 samples
   8 predictor
   2 classes: 'NOT_BLIGHTED', 'BLIGHTED' 

No pre-processing
Resampling: Cross-Validated (5 fold, repeated 1 times) 
Summary of sample sizes: 6718, 6719, 6718, 6718, 6719 
Resampling results across tuning parameters:

  cp           ROC        Sens       Spec     
  0.004950495  0.7199713  0.5713362  0.8006339
  0.011774150  0.7120725  0.5412974  0.8284786
  0.246989564  0.6087019  0.6952790  0.5221249

ROC was used to select the optimal model using  the largest value.
The final value used for the model was cp = 0.004950495. 

Adding the neighborhood features seems to drop the performance slightly to 71.4% AUC (from 72.0% above)

# decision tree model
dtreeModel3 <- train(f_basic_neigh,
                  data = trainSet,
                  method = "rpart",
                  trControl = ctrl,
                  metric = "ROC",
                  na.action=na.exclude
                  )
dtreeModel3
CART 

8398 samples
  16 predictor
   2 classes: 'NOT_BLIGHTED', 'BLIGHTED' 

No pre-processing
Resampling: Cross-Validated (5 fold, repeated 1 times) 
Summary of sample sizes: 6719, 6718, 6717, 6719, 6719 
Resampling results across tuning parameters:

  cp           ROC        Sens       Spec     
  0.003612523  0.7145145  0.6166142  0.7393521
  0.009722594  0.6995196  0.5378796  0.8212572
  0.246989564  0.5715752  0.7913130  0.3518373

ROC was used to select the optimal model using  the largest value.
The final value used for the model was cp = 0.003612523. 

Adding all features slight worses the performance to 70.8%AUC

# decision tree model
dtreeModel4 <- train(f_all,
                  data = trainSet,
                  method = "rpart",
                  trControl = ctrl,
                  metric = "ROC",
                  na.action=na.exclude
                  )
dtreeModel4
CART 

8398 samples
 101 predictor
   2 classes: 'NOT_BLIGHTED', 'BLIGHTED' 

No pre-processing
Resampling: Cross-Validated (5 fold, repeated 1 times) 
Summary of sample sizes: 6718, 6719, 6717, 6719, 6719 
Resampling results across tuning parameters:

  cp           ROC        Sens       Spec     
  0.005887075  0.7079370  0.6453859  0.6954399
  0.009722594  0.7027594  0.6101928  0.7390811
  0.246989564  0.6078667  0.6912017  0.5245316

ROC was used to select the optimal model using  the largest value.
The final value used for the model was cp = 0.005887075. 

Removing the neighborhood features from all brings the performance back up slightly to 71.8% AUC

# decision tree model
dtreeModel5 <- train(f_all_noneigh,
                  data = trainSet,
                  method = "rpart",
                  trControl = ctrl,
                  metric = "ROC",
                  na.action=na.exclude
                  )
dtreeModel5
CART 

8398 samples
  93 predictor
   2 classes: 'NOT_BLIGHTED', 'BLIGHTED' 

No pre-processing
Resampling: Cross-Validated (5 fold, repeated 1 times) 
Summary of sample sizes: 6719, 6718, 6719, 6718, 6718 
Resampling results across tuning parameters:

  cp           ROC        Sens       Spec     
  0.004950495  0.7185167  0.5970636  0.7588955
  0.011774150  0.6973395  0.5434155  0.8166813
  0.246989564  0.6092584  0.6929185  0.5255983

ROC was used to select the optimal model using  the largest value.
The final value used for the model was cp = 0.004950495. 

Comparing the models:

resamps <- resamples(list(dtree_violonly = dtreeModel, dtree_plus311crime = dtreeModel2, dtree_plusneigh = dtreeModel3, dtree_pluscodes = dtreeModel4, dtree_all_minusneigh = dtreeModel5))
summary(resamps)

Call:
summary.resamples(object = resamps)

Models: dtree_violonly, dtree_plus311crime, dtree_plusneigh, dtree_pluscodes, dtree_all_minusneigh 
Number of resamples: 5 

ROC 
                       Min. 1st Qu. Median   Mean 3rd Qu.   Max. NA's
dtree_violonly       0.7118  0.7136 0.7141 0.7182  0.7166 0.7351    0
dtree_plus311crime   0.7119  0.7158 0.7193 0.7200  0.7263 0.7265    0
dtree_plusneigh      0.7061  0.7079 0.7105 0.7145  0.7229 0.7252    0
dtree_pluscodes      0.7013  0.7068 0.7080 0.7079  0.7109 0.7127    0
dtree_all_minusneigh 0.7093  0.7170 0.7176 0.7185  0.7228 0.7259    0

Sens 
                       Min. 1st Qu. Median   Mean 3rd Qu.   Max. NA's
dtree_violonly       0.5826  0.6524 0.6663 0.6505  0.6717 0.6795    0
dtree_plus311crime   0.5461  0.5644 0.5655 0.5713  0.5734 0.6073    0
dtree_plusneigh      0.5697  0.5777 0.5891 0.6166  0.6223 0.7242    0
dtree_pluscodes      0.5016  0.6674 0.6770 0.6454  0.6856 0.6953    0
dtree_all_minusneigh 0.5408  0.5687 0.5880 0.5971  0.6041 0.6838    0

Spec 
                       Min. 1st Qu. Median   Mean 3rd Qu.   Max. NA's
dtree_violonly       0.6497  0.6524 0.6693 0.6867  0.6867 0.7751    0
dtree_plus311crime   0.7523  0.8046 0.8102 0.8006  0.8139 0.8222    0
dtree_plusneigh      0.6265  0.7166 0.7738 0.7394  0.7764 0.8035    0
dtree_pluscodes      0.6292  0.6310 0.6426 0.6954  0.6734 0.9011    0
dtree_all_minusneigh 0.6506  0.7206 0.7938 0.7589  0.8048 0.8246    0

Let’s try a random forest with all the variables. Being a more complex model, it seems to reach to a new best performance of 73.2% AUC on ROC (1.2% better than the best dtree), with 61.8% True Positive (TP), and 73.9% True Negative (TN)

# random forest model
rfModel2 <- train(condition ~ .,
                  data = trainSet,
                  method = "rf",
                  trControl = ctrl,
                  metric = "ROC"
                  )
rfModel2
Random Forest 

8398 samples
 101 predictor
   2 classes: 'NOT_BLIGHTED', 'BLIGHTED' 

No pre-processing
Resampling: Cross-Validated (5 fold, repeated 1 times) 
Summary of sample sizes: 6718, 6718, 6719, 6719, 6718 
Resampling results across tuning parameters:

  mtry  ROC        Sens       Spec     
    2   0.7316718  0.6179107  0.7385721
   51   0.7069456  0.6865531  0.6237667
  101   0.7037194  0.6891292  0.6202883

ROC was used to select the optimal model using  the largest value.
The final value used for the model was mtry = 2. 
rf2varImp <- varImp(rfModel2, scale = FALSE)
plot(rf2varImp, top=28)

Retraining Random Forest with only most important features. Using the top 17 features, the performance drops 0.01% to 72.9% AUC (compared to the RF model with 101 features)

trainSet2 <- trainSet %>% select(num_viols, max_amt, num_responsible, compliance, hazard, num_crime, waste, other, num_respons_neigh, total_max_amt_neigh, num_viols_neigh, avg_max_amt_neigh, diff_rating, num_crime_neigh, min_rating, num_311, max_rating, condition)
# random forest model
rfModel3 <- train(condition ~ .,
                  data = trainSet2,
                  method = "rf",
                  trControl = ctrl,
                  metric = "ROC"
                  )

We’ll try also gradient boosting. Let’s get started prepping the data for XGBoost

dtrain <- Matrix::sparse.model.matrix(condition ~ ., data=trainSet)
deval <- Matrix::sparse.model.matrix(condition ~ ., data=validSet)

Training and cross-validation of GBM with XGBoost Kudos to James Marquez for the awesome XGBoost recipe See here: www.jamesmarquezportfolio.com

grid <- expand.grid(nrounds = c(300, 400, 500, 600),
                    max_depth = c(3, 5, 7, 9),
                    eta = c(0.05, 0.1, 0.2, 0.3),
                    gamma = 1,
                    colsample_bytree = c(0.5, 1),
                    min_child_weight = 1,
                    subsample = 1)
ctrl <- trainControl(method = "cv",
                     number = 5,
                     summaryFunction = twoClassSummary,
                     classProbs = TRUE,
                     allowParallel = TRUE )
set.seed(107)
system.time(xgbTune <- train(x = dtrain,
                             y = factor(trainSet$condition),
                             method = "xgbTree",
                             metric = "ROC",
                             tuneGrid = grid,
                             verbose = TRUE,
                             trControl = ctrl))
The training data could not be converted to a data frame for saving
    user   system  elapsed 
2084.519   19.757  565.640 

Gradient boosting reaches the best performance in crossvalidation, of 73.5% ROC/AUC, with a 63.6% true positive, and 71.2% true negative rates. The optimal hyperparmeters being: nrounds = 300, max_depth = 3, eta = 0.05, gamma = 1, colsample_bytree = 1, min_child_weight = 1 and subsample = 1.

xgbTune
eXtreme Gradient Boosting 

No pre-processing
Resampling: Cross-Validated (5 fold) 
Summary of sample sizes: 6718, 6718, 6718, 6719, 6719 
Resampling results across tuning parameters:

  eta   max_depth  colsample_bytree  nrounds  ROC        Sens       Spec     
  0.05  3          0.5               300      0.6189223  0.4677120  0.7618438
  0.05  3          0.5               400      0.6189223  0.4677120  0.7618438
  0.05  3          0.5               500      0.6189223  0.4677120  0.7618438
  0.05  3          0.5               600      0.6189223  0.4677120  0.7618438
  0.05  3          1.0               300      0.7346546  0.6363473  0.7120707
  0.05  3          1.0               400      0.7344732  0.6372057  0.7118030
  0.05  3          1.0               500      0.7344732  0.6372057  0.7118030
  0.05  3          1.0               600      0.7344732  0.6372057  0.7118030
  0.05  5          0.5               300      0.6174453  0.4644934  0.7637169
  0.05  5          0.5               400      0.6174453  0.4644934  0.7637169
  0.05  5          0.5               500      0.6174453  0.4644934  0.7637169
  0.05  5          0.5               600      0.6174453  0.4644934  0.7637169
  0.05  5          1.0               300      0.7336117  0.6412797  0.7053809
  0.05  5          1.0               400      0.7335572  0.6414943  0.7051135
  0.05  5          1.0               500      0.7335572  0.6414943  0.7051135
  0.05  5          1.0               600      0.7335572  0.6414943  0.7051135
  0.05  7          0.5               300      0.6160731  0.4593443  0.7669276
  0.05  7          0.5               400      0.6160731  0.4593443  0.7669276
  0.05  7          0.5               500      0.6160731  0.4593443  0.7669276
  0.05  7          0.5               600      0.6160731  0.4593443  0.7669276
  0.05  7          1.0               300      0.7332625  0.6404206  0.7024322
  0.05  7          1.0               400      0.7332625  0.6404206  0.7024322
  0.05  7          1.0               500      0.7332625  0.6404206  0.7024322
  0.05  7          1.0               600      0.7332625  0.6404206  0.7024322
  0.05  9          0.5               300      0.6156239  0.4580572  0.7679975
  0.05  9          0.5               400      0.6156239  0.4580572  0.7679975
  0.05  9          0.5               500      0.6156239  0.4580572  0.7679975
  0.05  9          0.5               600      0.6156239  0.4580572  0.7679975
  0.05  9          1.0               300      0.7294676  0.6472876  0.6893313
  0.05  9          1.0               400      0.7294676  0.6472876  0.6893313
  0.05  9          1.0               500      0.7294676  0.6472876  0.6893313
  0.05  9          1.0               600      0.7294676  0.6472876  0.6893313
  0.10  3          0.5               300      0.6185266  0.4672824  0.7618442
  0.10  3          0.5               400      0.6185266  0.4672824  0.7618442
  0.10  3          0.5               500      0.6185266  0.4672824  0.7618442
  0.10  3          0.5               600      0.6185266  0.4672824  0.7618442
  0.10  3          1.0               300      0.7337158  0.6397782  0.7112672
  0.10  3          1.0               400      0.7337158  0.6397782  0.7112672
  0.10  3          1.0               500      0.7337158  0.6397782  0.7112672
  0.10  3          1.0               600      0.7337158  0.6397782  0.7112672
  0.10  5          0.5               300      0.6188520  0.4638494  0.7642517
  0.10  5          0.5               400      0.6188520  0.4638494  0.7642517
  0.10  5          0.5               500      0.6188520  0.4638494  0.7642517
  0.10  5          0.5               600      0.6188520  0.4638494  0.7642517
  0.10  5          1.0               300      0.7327701  0.6372054  0.7088575
  0.10  5          1.0               400      0.7327701  0.6372054  0.7088575
  0.10  5          1.0               500      0.7327701  0.6372054  0.7088575
  0.10  5          1.0               600      0.7327701  0.6372054  0.7088575
  0.10  7          0.5               300      0.6176513  0.4604166  0.7677301
  0.10  7          0.5               400      0.6176513  0.4604166  0.7677301
  0.10  7          0.5               500      0.6176513  0.4604166  0.7677301
  0.10  7          0.5               600      0.6176513  0.4604166  0.7677301
  0.10  7          1.0               300      0.7315357  0.6436402  0.6930703
  0.10  7          1.0               400      0.7315357  0.6436402  0.6930703
  0.10  7          1.0               500      0.7315357  0.6436402  0.6930703
  0.10  7          1.0               600      0.7315357  0.6436402  0.6930703
  0.10  9          0.5               300      0.6149977  0.4567694  0.7682649
  0.10  9          0.5               400      0.6149977  0.4567694  0.7682649
  0.10  9          0.5               500      0.6149977  0.4567694  0.7682649
  0.10  9          0.5               600      0.6149977  0.4567694  0.7682649
  0.10  9          1.0               300      0.7282451  0.6436393  0.6863826
  0.10  9          1.0               400      0.7282451  0.6436393  0.6863826
  0.10  9          1.0               500      0.7282451  0.6436393  0.6863826
  0.10  9          1.0               600      0.7282451  0.6436393  0.6863826
  0.20  3          0.5               300      0.6183800  0.4664245  0.7629137
  0.20  3          0.5               400      0.6183800  0.4664245  0.7629137
  0.20  3          0.5               500      0.6183800  0.4664245  0.7629137
  0.20  3          0.5               600      0.6183800  0.4664245  0.7629137
  0.20  3          1.0               300      0.7336891  0.6427830  0.7051114
  0.20  3          1.0               400      0.7336891  0.6427830  0.7051114
  0.20  3          1.0               500      0.7336891  0.6427830  0.7051114
  0.20  3          1.0               600      0.7336891  0.6427830  0.7051114
  0.20  5          0.5               300      0.6180756  0.4610606  0.7666602
  0.20  5          0.5               400      0.6180756  0.4610606  0.7666602
  0.20  5          0.5               500      0.6180756  0.4610606  0.7666602
  0.20  5          0.5               600      0.6180756  0.4610606  0.7666602
  0.20  5          1.0               300      0.7318531  0.6436391  0.7016275
  0.20  5          1.0               400      0.7318531  0.6436391  0.7016275
  0.20  5          1.0               500      0.7318531  0.6436391  0.7016275
  0.20  5          1.0               600      0.7318531  0.6436391  0.7016275
  0.20  7          0.5               300      0.6154024  0.4591290  0.7674616
  0.20  7          0.5               400      0.6154024  0.4591290  0.7674616
  0.20  7          0.5               500      0.6154024  0.4591290  0.7674616
  0.20  7          0.5               600      0.6154024  0.4591290  0.7674616
  0.20  7          1.0               300      0.7292590  0.6479293  0.6815587
  0.20  7          1.0               400      0.7292590  0.6479293  0.6815587
  0.20  7          1.0               500      0.7292590  0.6479293  0.6815587
  0.20  7          1.0               600      0.7292590  0.6479293  0.6815587
  0.20  9          0.5               300      0.6145506  0.4574132  0.7690681
  0.20  9          0.5               400      0.6145506  0.4574132  0.7690681
  0.20  9          0.5               500      0.6145506  0.4574132  0.7690681
  0.20  9          0.5               600      0.6145506  0.4574132  0.7690681
  0.20  9          1.0               300      0.7261859  0.6578033  0.6687216
  0.20  9          1.0               400      0.7261859  0.6578033  0.6687216
  0.20  9          1.0               500      0.7261859  0.6578033  0.6687216
  0.20  9          1.0               600      0.7261859  0.6578033  0.6687216
  0.30  3          0.5               300      0.6178397  0.4632060  0.7631818
  0.30  3          0.5               400      0.6178397  0.4632060  0.7631818
  0.30  3          0.5               500      0.6178397  0.4632060  0.7631818
  0.30  3          0.5               600      0.6178397  0.4632060  0.7631818
  0.30  3          1.0               300      0.7308070  0.6423474  0.7013627
  0.30  3          1.0               400      0.7308070  0.6423474  0.7013627
  0.30  3          1.0               500      0.7308070  0.6423474  0.7013627
  0.30  3          1.0               600      0.7308070  0.6423474  0.7013627
  0.30  5          0.5               300      0.6157272  0.4589140  0.7671950
  0.30  5          0.5               400      0.6157272  0.4589140  0.7671950
  0.30  5          0.5               500      0.6157272  0.4589140  0.7671950
  0.30  5          0.5               600      0.6157272  0.4589140  0.7671950
  0.30  5          1.0               300      0.7300087  0.6472887  0.6917288
  0.30  5          1.0               400      0.7300087  0.6472887  0.6917288
  0.30  5          1.0               500      0.7300087  0.6472887  0.6917288
  0.30  5          1.0               600      0.7300087  0.6472887  0.6917288
  0.30  7          0.5               300      0.6137818  0.4567687  0.7688007
  0.30  7          0.5               400      0.6137818  0.4567687  0.7688007
  0.30  7          0.5               500      0.6137818  0.4567687  0.7688007
  0.30  7          0.5               600      0.6137818  0.4567687  0.7688007
  0.30  7          1.0               300      0.7249734  0.6590897  0.6716760
  0.30  7          1.0               400      0.7249734  0.6590897  0.6716760
  0.30  7          1.0               500      0.7249734  0.6590897  0.6716760
  0.30  7          1.0               600      0.7249734  0.6590897  0.6716760
  0.30  9          0.5               300      0.6126548  0.4518343  0.7704053
  0.30  9          0.5               400      0.6126548  0.4518343  0.7704053
  0.30  9          0.5               500      0.6126548  0.4518343  0.7704053
  0.30  9          0.5               600      0.6126548  0.4518343  0.7704053
  0.30  9          1.0               300      0.7226647  0.6584438  0.6542799
  0.30  9          1.0               400      0.7226647  0.6584438  0.6542799
  0.30  9          1.0               500      0.7226647  0.6584438  0.6542799
  0.30  9          1.0               600      0.7226647  0.6584438  0.6542799

Tuning parameter 'gamma' was held constant at a value of 1
Tuning
 parameter 'min_child_weight' was held constant at a value of 1
Tuning
 parameter 'subsample' was held constant at a value of 1
ROC was used to select the optimal model using  the largest value.
The final values used for the model were nrounds = 300, max_depth = 3, eta = 0.05, gamma
 = 1, colsample_bytree = 1, min_child_weight = 1 and subsample = 1. 
ggplot(xgbTune) +
  theme(legend.position = "top")
Ignoring unknown aesthetics: shape

Training a GBM with the best parameters, yields to 78.7% AUC

trainlab <- trainSet %>%
  select(condition) %>%
  mutate(lab = ifelse((condition=="BLIGHTED"),1,0)) %>%
  select(lab)
param <- list(objective = 'binary:logistic',
              eval_metric = 'auc',
              max_depth = 3, 
              eta = 0.05, 
              gamma = 1, 
              colsample_bytree = 1, 
              min_child_weight = 1,
              subsample = 1)
set.seed(107)
system.time(xgb <- xgboost(params = param, 
                           data = dtrain,
                           label = trainlab$lab,
                           nrounds = 300,
                           print_every_n = 100,
                           verbose = 1))
[1] train-auc:0.728302 
[101]   train-auc:0.766306 
[201]   train-auc:0.778231 
[300]   train-auc:0.787431 
   user  system elapsed 
  1.452   0.007   1.459 

Below is a plot of feature importance

model <- xgb.dump(xgb, with_stats = TRUE)
names <- dimnames(dtrain)[[2]]
importance_matrix <- xgb.importance(names, model = xgb)[0:30]
xgb.plot.importance(importance_matrix)

Plotting the ROC, we can explore different values of true positive and false positive rates.

validlab <- validSet %>%
  select(condition) %>%
  mutate(lab = ifelse((condition=="BLIGHTED"),1,0)) %>%
  select(lab)
xgbVal <- predict(xgb, newdata = deval)
xgb.pred <- ROCR::prediction(xgbVal, validlab$lab)
xgb.perf <- ROCR::performance(xgb.pred, "tpr", "fpr")
auc <- ROCR::performance(xgb.pred,"auc")
auc <- unlist(slot(auc, "y.values"))
auc<-round(auc, digits = 3)
auct <- paste(c("AUC  = "),auc,sep="")
plot(xgb.perf,
     avg="threshold",
     colorize=TRUE,
     lwd=3,
     print.cutoffs.at=seq(0, 1, by=0.05),
     text.adj=c(-0.5, 0.5),
     text.cex=0.6)
grid(col="lightgray")
axis(1, at=seq(0, 1, by=0.1))
axis(2, at=seq(0, 1, by=0.1))
abline(v=c(0.1, 0.3, 0.5, 0.7, 0.9), col="lightgray", lty="dotted")
abline(h=c(0.1, 0.3, 0.5, 0.7, 0.9), col="lightgray", lty="dotted")
lines(x=c(0, 1), y=c(0, 1), col="black", lty="dotted")
#adding min/max AUC in the plot
legend(0.5,0.5,c(auct),border="white",cex=1.2,box.col = "white")

Let’s check the results in the validation set for the optimal operating point, around 0.47 yields to 74.7% TP and 63.9% TN

xgbVal <- predict(xgb, newdata = deval)
xgbVal.resp <- ifelse(xgbVal > 0.5, 1, 0)
confusionMatrix(xgbVal.resp, validlab$lab, positive = '1')
Confusion Matrix and Statistics

          Reference
Prediction   0   1
         0 913 258
         1 476 636
                                          
               Accuracy : 0.6785          
                 95% CI : (0.6589, 0.6976)
    No Information Rate : 0.6084          
    P-Value [Acc > NIR] : 2.192e-12       
                                          
                  Kappa : 0.3534          
 Mcnemar's Test P-Value : 1.151e-15       
                                          
            Sensitivity : 0.7114          
            Specificity : 0.6573          
         Pos Pred Value : 0.5719          
         Neg Pred Value : 0.7797          
             Prevalence : 0.3916          
         Detection Rate : 0.2786          
   Detection Prevalence : 0.4871          
      Balanced Accuracy : 0.6844          
                                          
       'Positive' Class : 1               
                                          

Other data to explore in the future: - Neighboorhood/region - Demographic data - MLS / Zillow (last time sold, price, # sold houses around) - Detroit Parcel data

LS0tCnRpdGxlOiAnQ291cnNlcmEgV2FzaFUgRHRhc2NpIENhcHN0b25lIFByb2plY3Q6IEJMSUdIVCcKYWx3YXlzX2FsbG93X2h0bWw6IHllcwpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAotLS0KCkxvYWRpbmcgbGlicmFyaWVzCmBgYHtyLCBpbmNsdWRlPUZBTFNFfQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KG1hZ3JpdHRyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KHBseXIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZnV6enlqb2luKQpsaWJyYXJ5KGx1Y3IpCmxpYnJhcnkobHVicmlkYXRlKQpsaWJyYXJ5KHJib2tlaCkKbGlicmFyeShnZW9zcGhlcmUpCmxpYnJhcnkoY2FyZXQpCiNsaWJyYXJ5KE1hdHJpeCkKbGlicmFyeSh4Z2Jvb3N0KQpsaWJyYXJ5KGRvUGFyYWxsZWwpCm4uY29yZXMgPC0gZGV0ZWN0Q29yZXMoKQpyZWdpc3RlckRvUGFyYWxsZWwobi5jb3JlcykKbi5jb3JlcyA8LSBnZXREb1BhcldvcmtlcnMoKQpwYXN0ZShuLmNvcmVzLCAnd29ya2VycyB1dGlsaXplZCcpCmBgYAoKU2V0dGluZyBkYXRhIGxvY2F0aW9uLCBtYWtlIHN1cmUgdGhlIGZpbGVzIGJlbG93IGFyZSBhdmFpbGFibGUgc28gdGhlIHJlc3QgcnVucy4KYGBge3J9CmRhdGFkaXIgPC0gIi4uL2RhdGEiCmxpc3QuZmlsZXMoZGF0YWRpcikKYGBgCgpGaXJzdCB3ZSBsb2FkIGFsbCBkYXRhCmBgYHtyLCBpbmNsdWRlPUZBTFNFfQpkYXRhX3Blcm1pdHMgPC0gcmVhZF90c3YocGFzdGUoZGF0YWRpciwgImRldHJvaXQtZGVtb2xpdGlvbi1wZXJtaXRzLnRzdiIsIHNlcD0iLyIpKQpkYXRhX3Zpb2xhdGlvbnMgPC0gcmVhZF9jc3YocGFzdGUoZGF0YWRpciwgImRldHJvaXQtYmxpZ2h0LXZpb2xhdGlvbnMuY3N2Iiwgc2VwPSIvIikpCmRhdGFfMzExIDwtIHJlYWRfY3N2KHBhc3RlKGRhdGFkaXIsICJkZXRyb2l0LTMxMS5jc3YiLCBzZXA9Ii8iKSkKZGF0YV9jcmltZSA8LSByZWFkX2NzdihwYXN0ZShkYXRhZGlyLCAiZGV0cm9pdC1jcmltZS5jc3YiLCBzZXA9Ii8iKSkKYGBgCgpEYXRhIHBlcm1pdHMgZXhwbG9yYXRpb24KYGBge3J9CmhlYWQoZGF0YV9wZXJtaXRzKQpgYGAKCkZpcnN0IGNvbnZlcnRpbmcgZGF0YSBmaWVsZHMgdG8gdGhlIHJpZ2h0IHR5cGVzCmBgYHtyfQojY29udmVydGluZyB0byBmYWN0b3JzCmNvbHMgPC0gYygiQ0FTRV9UWVBFIiwgIkNBU0VfREVTQ1JJUFRJT04iLCAiTEVHQUxfVVNFIiwgIkJMRF9QRVJNSVRfVFlQRSIsCiAgICAgICAgICAiUEVSTUlUX0RFU0NSSVBUSU9OIiwgIkJMRF9QRVJNSVRfREVTQyIsICJCTERfVFlQRV9VU0UiLCAiUkVTSURFTlRJQUwiLAogICAgICAgICAgIkRFU0NSSVBUSU9OIiwgIkJMRF9UWVBFX0NPTlNUX0NPRCIsICJCTERfWk9OSU5HX0RJU1QiLCAiQkxEX1VTRV9HUk9VUCIsCiAgICAgICAgICAiQkxEX0JBU0VNRU5UIiwgIkZFRV9UWVBFIiwgIkNTRl9DUkVBVEVEX0JZIiwiQ09ORElUSU9OX0ZPUl9BUFBST1ZBTCIpCmRhdGFfcGVybWl0cyAlPD4lIG11dGF0ZV9lYWNoXyhmdW5zKGZhY3RvciguKSksY29scykKCiNjb252ZXJ0aW5nICQkIHRvIG51bWVyaWMKY29scyA8LSBjKCJQQ0ZfQU1UX1BEIiwgIlBDRl9BTVRfRFVFIiwgIlBDRl9VUERBVEVEIiwiRVNUSU1BVEVEX0NPU1QiKQpkYXRhX3Blcm1pdHMgJTw+JSBtdXRhdGVfZWFjaF8oZnVucyhmcm9tX2N1cnJlbmN5KC4pKSxjb2xzKQoKI2NvbnZlcnRpbmcgdG8gZGF0ZXMKY29scyA8LWMoIlBFUk1JVF9BUFBMSUVEIiwiUEVSTUlUX0lTU1VFRCIsIlBFUk1JVF9FWFBJUkVTIikKZGF0YV9wZXJtaXRzICU8PiUgbXV0YXRlX2VhY2hfKGZ1bnMocGFyc2VfZGF0ZV90aW1lKC4sb3JkZXJzPSJtZHkiLHR6PSJBbWVyaWNhL0RldHJvaXQiKSksY29scykKc3VtbWFyeShkYXRhX3Blcm1pdHMpCmBgYApFeHRyYWN0aW5nIGJ1aWxkaW5nIGxhdCBsb25ncyBmcm9tICJzaXRlX2xvY2F0aW9uIiB2YXJpYWJsZQpgYGB7cn0KZGF0YV9wZXJtaXRzICU8PiUKICAjZmlsdGVyIG91dCBwZXJtaXRzIHRoYXQgaGF2ZSBubyBsYXQvbG9uZwogIGZpbHRlcihncmVwbCgiXFwoWzAtOVxcLlxcLV0rLCAqWzAtOVxcLlxcLV0rXFwpIixzaXRlX2xvY2F0aW9uKSkgJT4lCiAgI2V4dHJhY3RpbmcgbGF0IGxvbmdzCiAgbXV0YXRlKGxhdCA9IGFzLmRvdWJsZShzdWIoIi4qXFwoKFswLTlcXC5cXC1dKyksLioiLCJcXDEiLCBzaXRlX2xvY2F0aW9uKSkpICU+JQogIG11dGF0ZShsb25nID0gYXMuZG91YmxlKHN1YigiLiosICooWzAtOVxcLlxcLV0rKS4qIiwiXFwxIiwgc2l0ZV9sb2NhdGlvbikpKSAlPiUKICBtdXRhdGUoYWRkcmVzc19vbmx5ID0gc3ViKCIoW15cXChdKylcXChbMC05XFwuXFwtXSssLioiLCJcXDEiLCBzaXRlX2xvY2F0aW9uKSkKYGBgCgpDcmVhdGUgYSBsaXN0IG9mIGJ1aWxkaW5ncyBkb2luZyBzb21lIG1hZ2ljIHRvIHJlbW92ZSB0aG9zZSBlbnRyaWVzIHdob3NlIGxhdC9sb25nIHN0ZGV2IGlzIGxhcmdlciB0aGFuIDEwZS00ICh+MTFtKS4gTm90IG11Y2ggZ2V0cyByZW1vdmVkIGFjdHVhbGx5LCBidXQgaXQncyBjb25zaXN0ZW50IHdpdGggc3RlcHMgYmVsb3cuIFJlbW92ZWQgdGhvc2UgcmVjb3JkcyB3aG9zZSBhZGRyZXNzIHdhcyBtaXNzaW5nIChvbmx5IH40MCkuCmBgYHtyfQpibGRfbGlzdF9wZXJtaXQgPC0gZGF0YV9wZXJtaXRzICU+JQogIG11dGF0ZShyID0gc3FydChQQVJDRUxfU0laRS9waSkgKSAlPiUKICBzZWxlY3QoYWRkcmVzcz1hZGRyZXNzX29ubHksIFBBUkNFTF9OTywgTE9UX05VTUJFUiwgUEVSTUlUX0lTU1VFRCwgUEFSQ0VMX1NJWkUsIGxhdCwgbG9uZywgcikgJT4lCiAgZmlsdGVyKCEgZ3JlcGwoIlxcKFswLTlcXC5cXC1dKywgKlswLTlcXC5cXC1dK1xcKSIsYWRkcmVzcykpICU+JQogIGFycmFuZ2UoYWRkcmVzcywgZGVzYyhQRVJNSVRfSVNTVUVEKSkgJT4lCiAgZ3JvdXBfYnkoYWRkcmVzcykgJT4lCiAgbXV0YXRlKHNkbGF0PXNkKGxhdCksIHNkbG9uZz1zZChsb25nKSkgJT4lCiAgZmlsdGVyKChzZGxhdDwxMGUtNCAmIHNkbG9uZzwxMGUtNCkgfCAoaXMubmEoc2RsYXQpIHwgaXMubmEoc2Rsb25nKSkpICU+JQogIGZpbHRlcihsb25nID4gLTgzLjMgJiBsb25nIDwgLTgyLjgpICU+JQogIGZpbHRlcihsYXQgPiA0Mi4yICYgbGF0IDwgNDIuNSkgJT4lCiAgYXJyYW5nZShQRVJNSVRfSVNTVUVEKSAlPiUKICBzdW1tYXJpc2Uobl9wZXJtaXRzPW4oKSwgbGFzdF9wZXJtaXQ9bGFzdChQRVJNSVRfSVNTVUVEKSwgCiAgICAgICAgICAgIGxhdD1tZWRpYW4obGF0KSwgbG9uZz1tZWRpYW4obG9uZyksIHI9bGFzdChyKSkKaGVhZChibGRfbGlzdF9wZXJtaXQpCmBgYAoKRnVuY3Rpb24gdG8gcmV0dXJuIG51bWJlciBvZiBibGlndGhlZCByZWNvcmRzIGZvciBhIHNwZWNpZmljIGxhdC9sb25nIGNvb3JkaW5hdGUuIFRoaXMgZnVuY3Rpb24gaXMgdXNlZCB0byBhc3NpZ24gYmxpZ2h0IGNvbXB1dGluZyB0aGUgZGlzdGFuY2UgZGlyZWN0bHkgaW4gZGVncmVlcyAwLjAwMDEgfiAxMW0gfiAzN2Z0LCB3aGljaCBpcyBmYXN0ZXIgdGhhbiB1c2luZyBhIGJ1aWx0IGluIGZ1bmN0aW9uIHN1Y2ggYXMgZGlzdEdlby4KCi0gZGF0YSwgYSBkYXRhZnJhbWUgY29udGFpbmluZyBhbGwgZGVtb2xpdGlvbnMgcGVybWl0cyBhcyBhIHByb3h5IGZvciBhc3NpZ25pbmcgYmxpZ2h0LiBUaGlzIGRhdGFmcmFtZSBpbmNsdWRlczoKICAtIGxhdDogbGF0IGNvb3JkaW5hdGUgb2YgZGVtb2xpdGlvbiBwZXJtaXQKICAtIGxvbmc6IGxvbmcgY29vcmRpbmF0ZSBvZiBkZW1vbGl0aW9uIHBlcm1pdAogIC0gcjogZXN0aW1hdGVkIGJ1aWxkaW5nJ3MgYXJlYSByYWRpb3VzICh3aGVuIGFyZWEgaXMgYXBwcm94aW1hdGVkIHRvIGNpcmNsZSkKLSB0aGUgb3RoZXIgaW5wdXQgdmFyaWFibGVzIGFyZToKICAtIGx0OiB0YXJnZXQgbGF0IGNvb3JkaW5hdGUKICAtIGxuOiB0YXJnZXQgbG9uZyBjb29yZGluYXRlCmBgYHtyfQppbl9ibGlnaHQgPC0gZnVuY3Rpb24obHQsIGxuLCBkYXRhKSB7CiAgZGF0YSAlPiUgCiAgICBmaWx0ZXIoc3FydCgobGF0LWx0KV4yKyhsb25nLWxuKV4yKTxyZGVnciswLjAwMDEpICU+JQogICAgbnJvdygpCn0KCiNjcmVhdGluZyB0aGUgZGF0YWZyYW1lIG9mIGJsaWd0aGVkIGJ1aWxkaW5ncwppbmRhdGEgPC0gYmxkX2xpc3RfcGVybWl0ICU+JSAKICBmaWx0ZXIoIWlzLm5hKHIpKSAlPiUKICB1bmlxdWUoKSAlPiUKICBzZWxlY3QoYWRkcmVzcywgbGF0LCBsb25nLCByKQoKaW5kYXRhX2RlZ3JlZXMgPC0gaW5kYXRhICU+JQogIG11dGF0ZShyZGVnciA9IDAuMDAwMS8zNyAqIHIpCmBgYAoKTG9hZGluZyBibGlnaHQgdmlvbGF0aW9uIGluY2lkZW50cwpgYGB7cn0KaGVhZChkYXRhX3Zpb2xhdGlvbnMpCmBgYAoKRGF0YSBWaW9sYXRpb24gZXhwbG9yYXRpb24sIGNvbnZlcnRpbmcgZmlyc3QgdG8gdGhlIHJpZ2h0IGRhdGEgZmllbGRzCmBgYHtyfQojY29udmVydGluZyB0byBmYWN0b3JzCmNvbHMgPC0gYygiQWdlbmN5TmFtZSIsIlZpb2xhdGlvbkNvZGUiLCJEaXNwb3NpdGlvbiIsIlBheW1lbnRTdGF0dXMiLCJWb2lkIiwKICAgICAgICAgICJWaW9sYXRpb25DYXRlZ29yeSIsIkNvdW50cnkiKQpkYXRhX3Zpb2xhdGlvbnMgJTw+JSBtdXRhdGVfZWFjaF8oZnVucyhmYWN0b3IoLikpLGNvbHMpCgojY29udmVydGluZyAkJCB0byBudW1lcmljCmNvbHMgPC0gYygiRmluZUFtdCIsIkFkbWluRmVlIiwiTGF0ZUZlZSIsIlN0YXRlRmVlIiwiQ2xlYW5VcENvc3QiLCJKdWRnbWVudEFtdCIpCmRhdGFfdmlvbGF0aW9ucyAlPD4lIG11dGF0ZV9lYWNoXyhmdW5zKGZyb21fY3VycmVuY3koLikpLGNvbHMpCgojbm90IGNvbnZlcnRpbmcgdG8gZGF0ZXMgYXMgdGhlIGRhdGVzIGluIHRoZSBmaWVsZHMgYmVsb3cgaGF2ZSB3ZWlyZCB5ZWFycwpjb2xzIDwtYygiVGlja2V0SXNzdWVkRFQiLCJIZWFyaW5nRFQiKQojZGF0YV92aW9sYXRpb25zICU8PiUgbXV0YXRlX2VhY2hfKGZ1bnMoZnJvbV9jdXJyZW5jeSguKSksY29scykKc3VtbWFyeShkYXRhX3Zpb2xhdGlvbnMpCmBgYAoKR2V0dGluZyB0aGUgdmlvbGF0aW9uIGNvZGVzLCB3ZSdsbCBtYW51YWxseSBjYXRlZ29yaXplIHRoZW0gYmVsb3cKYGBge3J9CnZpb2xDb2RlcyA8LSBkYXRhX3Zpb2xhdGlvbnMgJT4lIAogIHNlbGVjdChWaW9sYXRpb25Db2RlLCBWaW9sRGVzY3JpcHRpb24pICU+JQogIHVuaXF1ZSgpCmBgYAoKR2VuZXJhdGVkIGxhdC9sb25ncyBhbmQgYWRkcmVzcywgYW5kIGNsZWFuZWQgZGF0YSBieSBrZWVwaW5nIHJlY29yZHMgd2l0aCBEZXRyb2l0IGFkZHJlc3NlcyBvbmx5LiBEaWRuJ3QgZmlsdGVyIGJ5IGNvdW50cnkgYXMgaXQgcmVtb3ZlZCBtb3N0IG9mIHRoZSBlbnRyaWVzIChmcm9tIDMwMGsgdG8gMTNrIHJlY29yZHMpLiBEaWRuJ3QgcmVtb3ZlIGRpc3Bvc2l0aW9uICJub3QgcmVwb3NvbnNpYmxlIiBvciAicGVuZGluZyIgYXMgdGhpcyBjb3VsZCBjb250YWluIGluZm9ybWF0aW9uCmBgYHtyfQp2aW9sX2xpc3QgPC0gZGF0YV92aW9sYXRpb25zICU+JSAKIyAgZmlsdGVyKENvdW50cnkgPT0gIlVTIikgJT4lCiAgZmlsdGVyKGdyZXBsKCJcXChbMC05XFwuXFwtXSssICpbMC05XFwuXFwtXStcXCkiLFZpb2xhdGlvbkFkZHJlc3MpKSAlPiUKICAjZXh0cmFjdGluZyBsYXQgbG9uZ3MKICBtdXRhdGUobGF0ID0gYXMuZG91YmxlKHN1YigiLipcXCgoWzAtOVxcLlxcLV0rKSwuKiIsIlxcMSIsIFZpb2xhdGlvbkFkZHJlc3MpKSkgJT4lCiAgbXV0YXRlKGxvbmcgPSBhcy5kb3VibGUoc3ViKCIuKiwgKihbMC05XFwuXFwtXSspLioiLCJcXDEiLCBWaW9sYXRpb25BZGRyZXNzKSkpICU+JQogIG11dGF0ZShhZGRyZXNzX29ubHkgPSBzdWIoIihbXlxcKF0rKVxcKFswLTlcXC5cXC1dKywuKiIsIlxcMSIsIFZpb2xhdGlvbkFkZHJlc3MpKSAlPiUKICBmaWx0ZXIoZ3JlcGwoIkRldHJvaXQiLFZpb2xhdGlvbkFkZHJlc3MpKSAlPiUKIyAgZmlsdGVyKCEgZ3JlcGwoIk5vdCByZXNwb25zaWJsZSIsRGlzcG9zaXRpb24pKSAlPiUKIyAgZmlsdGVyKCEgZ3JlcGwoIlBFTkRJTkciLCBEaXNwb3NpdGlvbikpICU+JQogIHNlbGVjdChsYXQsIGxvbmcsIFZpb2xhdGlvbkNvZGUsIERpc3Bvc2l0aW9uLCBKdWRnbWVudEFtdCwgUGF5bWVudFN0YXR1cywgVmlvbGF0aW9uQ2F0ZWdvcnksIGFkZHJlc3Nfb25seSkgCgpoZWFkKHZpb2xfbGlzdCkKYGBgCgpXaGF0IGhhcHBlbnMgd2l0aCBEaXNwb3NpdGlvbj8gV2VsbCwgaXQgc2VlbXMgaGF0IGl0IGNvdWxkIGJlIGNhdGVnb3JpemVkIGFzIHJlc3BvbnNpYmxlLCBub3QgcmVzcG9uc2libGUsIGFuZCBwZW5kaW5nCmBgYHtyfQp2aW9sX2xpc3QgJT4lCiAgc2VsZWN0KERpc3Bvc2l0aW9uKSAlPiUKICBncm91cF9ieShEaXNwb3NpdGlvbikgJT4lCiAgc3VtbWFyaXNlKG4oKSkKYGBgCgpUaGVyZSBhcmUgYWJvdXQgODBrIHVuaXF1ZSBsYXQvbG9uZ3MsIHNvbWUgb2YgdGhlbSBnZW5lcmF0ZSBhIGh1Z2UgYW1vdW50IG9mIHZpb2xhdGlvbnMsIGhlcmUgYXJlIHRoZSB0b3AgMzAgbGF0L2xvbmdzCmBgYHtyfQp0b3AzMHZpb2xzIDwtIHZpb2xfbGlzdCAlPiUKICBzZWxlY3QobGF0LCBsb25nKSAlPiUKICBtdXRhdGUoZ2VvY29yZCA9IHBhc3RlKGxhdCxsb25nKSkgJT4lCiAgZ3JvdXBfYnkoZ2VvY29yZCkgJT4lCiAgc3VtbWFyaXplKGxhdD1sYXN0KGxhdCksIGxvbmc9bGFzdChsb25nKSwgbnVtX3Zpb2xzX2luX2dlbyA9IG4oKSkgJT4lCiAgYXJyYW5nZShkZXNjKG51bV92aW9sc19pbl9nZW8pKSAlPiUKICBoZWFkKDMwKQp0b3AzMHZpb2xzCmBgYAoKUGxvdHRpbmcgdGhlbSwgd2Ugc2VlIHRoYXQgdGhlIHRvcCBvbmUgaGFzIDIxayB2aW9sYXRpb25zIGFuZCBpdCdzIGluIHRoZSBjZW50ZXIgb2YgRGV0cm9pdCwgcHJvYmFibHkgYSBzdGFuZGFyZCBsYXQvbG9uZyBjb29yZGluYXRlIHdoZW4gbm90IHRoZSBhY3R1YWwgaXMgbm90IGF2YWlsYWJsZS4gSSdtIG5vdCBzdXJlIGFib3V0IHRoZSBvdGhlcnMgd2l0aCBvdmVyIDEwMDAgdmlvbGF0aW9ucy4uLiAKYGBge3J9CnAgPC0gZ21hcChsYXQgPSA0Mi4zNywgbG5nID0gLTgzLjEwLCB6b29tID0gMTEsIHdpZHRoID0gNjAwLCBoZWlnaHQgPSAzNTAsCiAgICAgICAgICBtYXBfc3R5bGUgPSBnbWFwX3N0eWxlKCJhcHBsZV9tYXBzZXNxdWUiKSkgJT4lCiAgbHlfcG9pbnRzKGxvbmcsIGxhdCwgZGF0YSA9IHRvcDMwdmlvbHMsIGhvdmVyID0gbnVtX3Zpb2xzX2luX2dlbywgCiAgICAgICAgICAgIGNvbCA9ICdyZWQnLCBhbHBoYSA9IHBtaW4obnVtX3Zpb2xzX2luX2dlbyAvIDEwMDAsIDEpKSAlPiUKICB4X2F4aXModmlzaWJsZSA9IEZBTFNFKSAlPiUKICB5X2F4aXModmlzaWJsZSA9IEZBTFNFKQpwCmBgYAoKQXJlIHRoZXJlIHRoZSBzYW1lIG51bWJlciBvZiB1bmlxdWUgYWRkcmVzc2VzPwpgYGB7cn0KdmlvbF9saXN0ICU+JQogIHNlbGVjdChhZGRyZXNzX29ubHkpICU+JQogIGdyb3VwX2J5KGFkZHJlc3Nfb25seSkgJT4lCiAgc3VtbWFyaXplKG51bV92aW9sc19pbl9hZGRyZXNzID0gbigpKSAlPiUKICBhcnJhbmdlKGRlc2MobnVtX3Zpb2xzX2luX2FkZHJlc3MpKQpgYGAKCldlbGwsIGl0IHNlZW1zIHRoYXQgdGhlcmUgYXJlIGFib3V0IDExMGsgdW5pcXVlIGFkZHJlc3NlcywgYW5kIDczayB1bmlxdWUgbGF0L2xvbmdzLiBMZXQncyBzZWUgaG93IHRoZXkgY29udHJhc3QgaW4gdGVybXMgb2YgbnVtYmVyIG9mIHZpb2xhdGlvbnMgKGxhdC9sb25nIHZzLiBhZGRyZXNzKQpgYGB7cn0KdmlvbF9saXN0X2NsZWFuZWQgPC0gdmlvbF9saXN0ICU+JQogIG11dGF0ZShnZW9jb29yZCA9IHBhc3RlKGxhdCxsb25nKSkgJT4lCiAgZ3JvdXBfYnkoZ2VvY29vcmQpICU+JQogIG11dGF0ZShudW1fdmlvbHNfaW5fZ2VvY29vcmQgPSBuKCkpCgp2aW9sX2xpc3RfY2xlYW5lZCAlPD4lIAogIGdyb3VwX2J5KGFkZHJlc3Nfb25seSkgJT4lCiAgbXV0YXRlKG51bV92aW9sc19pbl9hZGRyZXNzID0gbigpKQoKbnJvdyh2aW9sX2xpc3RfY2xlYW5lZCkKYGBgCgpJdCBzZWVtcyB0aGF0IGFib3V0IDcwayBlbnRyaWVzIGhhdmUgZGlmZmVyZW50IG51bWJlciBvZiB2aW9sYXRpb25zIHdoZW4gbG9va2luZyBieSBhZGRyZXNzIG9yIGxhdC9sb25nLCBiZWluZyAzNWsgdW5pcXVlIHJlY29yZHMgZHVwbGljYXRlZCwgc28gZ2V0dGluZyByaWQgb2YgdGhlbQpgYGB7cn0KdmlvbF9saXN0X2NsZWFuZWQgJTw+JQogIGZpbHRlcighIG51bV92aW9sc19pbl9hZGRyZXNzICE9IG51bV92aW9sc19pbl9nZW9jb29yZCkgJT4lCiAgZ3JvdXBfYnkoVmlvbGF0aW9uQ29kZSwgYWRkcmVzc19vbmx5KSAlPiUKICBtdXRhdGUobnVtX3Zpb2xzX2J5X3Zjb2RlID0gbigpKSAlPiUKICBhcnJhbmdlKGRlc2MobnVtX3Zpb2xzX2luX2dlb2Nvb3JkKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIHVuaXF1ZSgpIAoKbnJvdyh2aW9sX2xpc3RfY2xlYW5lZCkKYGBgCkFmdGVyIGNsZWFuaW5nLCBoZXJlIGFyZSB0aGUgdG9wIHZpb2xhdGlvbnMgY291bnRzIHBlciBnZW9jb29yZC9hZGRyZXNzCmBgYHtyfQp2aW9sX2xpc3RfY2xlYW5lZCAlPiUKICBzZWxlY3QoYWRkcmVzc19vbmx5LCBsYXQsIGxvbmcpICU+JQogIG11dGF0ZShnZW9jb3JkID0gcGFzdGUobGF0LGxvbmcpKSAlPiUKICBncm91cF9ieShnZW9jb3JkKSAlPiUKICBzdW1tYXJpemUoYWRkcmVzcz1sYXN0KGFkZHJlc3Nfb25seSksIGxhdD1sYXN0KGxhdCksIGxvbmc9bGFzdChsb25nKSwgbnVtX3Zpb2xzX2luX2dlbyA9IG4oKSkgJT4lCiAgYXJyYW5nZShkZXNjKG51bV92aW9sc19pbl9nZW8pKSAlPiUKICBzZWxlY3QoLWdlb2NvcmQpICU+JQogIGhlYWQoMzApCmBgYAoKVGhlcmUgYXJlIDMxMyB2aW9sYXRpb24gY29kZXMsIHNvbWUgb2YgdGhlbSBiZWluZyBtb3JlIGZyZXF1ZW50IHRoYW4gb3RoZXJzCmBgYHtyfQp2aW9sQ29kZXMgPC0gdmlvbF9saXN0X2NsZWFuZWQgJT4lIAojICBtdXRhdGUoVmlvbGF0aW9uQ29kZSA9IHN1YigiXihbMC05XSstWzAtOV0rKS0uKiQiLCJcXDEiLFZpb2xhdGlvbkNvZGUpKSAlPiUKICBncm91cF9ieShWaW9sYXRpb25Db2RlKSAlPiUKICB0YWxseShzb3J0PVRSVUUpIAoKdmlvbENvZGVzCmBgYApDYXRlZ29yaXppbmcgdGhlbSBzZW1hbnRpY2FsbHkgbWFudWFsbHksIHdlIHJlZHVjZSB0aGVtIHRvIDEyIGdyb3VwcywgbiBiZWluZyB0aGUgbnVtYmVyIG9mIGNhdGVnb3JpZXMgbWFwcGVkIHRvIGVhY2ggY29ycmVzcG9uZGluZyBncm91cApgYGB7cn0KdmlvbENvZGVzX21hbnVhbF9jYXRlZ29yaXphdGlvbiA8LQogIHJlYWRfY3N2KCJ2aW9sQ29kZXNfbWFudWFsX2NhdGVnb3JpemF0aW9uLmNzdiIpCgp2aW9sQ29kZXNfbWFudWFsX2NhdGVnb3JpemF0aW9uICU8PiUKICBtdXRhdGUoVmlvbEdyb3VwPWFzLmZhY3RvcihWaW9sR3JvdXApLAogICAgICAgICBWaW9sYXRpb25Db2RlPWFzLmZhY3RvcihWaW9sYXRpb25Db2RlKSkKCnZpb2xDb2Rlc19tYW51YWxfY2F0ZWdvcml6YXRpb24gJT4lIGdyb3VwX2J5KFZpb2xHcm91cCkgJT4lIHRhbGx5KHNvcnQgPSBUUlVFKQpgYGAKV2UgYWRkaW5nIHRoZSBncm91cGluZyBmYWN0b3IgZm9yIHZpb2xhdGlvbiBjYXRlZ29yaWVzLCBWaW9sR3JvdXAKYGBge3J9CnZpb2xfbGlzdF9jbGVhbmVkICU8PiUgCiAgbGVmdF9qb2luKHZpb2xDb2Rlc19tYW51YWxfY2F0ZWdvcml6YXRpb24sYnk9IlZpb2xhdGlvbkNvZGUiKQoKI1NvbWUgY29kZXMgaGFkIG5vIGRlc2NyaXB0aW9uLCB0byBwcmV2ZW50IHRvIE5BcywgZ3JvdXBpbmcgdGhlbSB0byBvdGhlcgp2aW9sX2xpc3RfY2xlYW5lZFt3aGljaChpcy5uYSh2aW9sX2xpc3RfY2xlYW5lZCRWaW9sR3JvdXApKSxdJFZpb2xHcm91cCA8LSAib3RoZXIiCgpoZWFkKHZpb2xfbGlzdF9jbGVhbmVkKQpgYGAKCkV4cGFuZCBWaW9sR3JvdXAgY291bnRzIGFzIHNlcGFyYXRlIGZlYXR1cmVzCmBgYHtyfQp2aW9sY29kZXNfY291bnRzIDwtIHZpb2xfbGlzdF9jbGVhbmVkICU+JQogIHNlbGVjdChhZGRyZXNzPWFkZHJlc3Nfb25seSwgbGF0LCBsb25nLCBWaW9sR3JvdXAsIERpc3Bvc2l0aW9uLCAKICAgICAgICAgSnVkZ21lbnRBbXQsIFBheW1lbnRTdGF0dXMpICU+JQogIGdyb3VwX2J5KGFkZHJlc3MsIFZpb2xHcm91cCkgJT4lCiAgc3VtbWFyaXplKG51bV92aW9sX2J5X2NvZGUgPSBuKCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBzcHJlYWQoVmlvbEdyb3VwLCBudW1fdmlvbF9ieV9jb2RlLCBmaWxsID0gMCkKCmhlYWQodmlvbGNvZGVzX2NvdW50cykKYGBgCgpHZXR0aW5nIGEgbGlzdCBvZiBidWlsZGluZ3MsIHRoZSBncm91cGluZyB3b3VsZG4ndCBiZSBuZWNlc3NhcnkgYXMgdGhlcmUgaXMgYSBvbmUgdG8gb25lIGxhdC9sb25nIHRvIGFkZHJlc3MgY29ycmVzcG9uZGFuY2UsIGJ1dCBsZWF2aW5nIGl0IGp1c3QgaW4gY2FzZS4gCmBgYHtyfQpibGRfbGlzdF92aW9sIDwtIHZpb2xfbGlzdF9jbGVhbmVkICU+JQogIHNlbGVjdChhZGRyZXNzPWFkZHJlc3Nfb25seSwgbGF0LCBsb25nLCBWaW9sYXRpb25Db2RlLCBEaXNwb3NpdGlvbiwgCiAgICAgICAgIEp1ZGdtZW50QW10LCBQYXltZW50U3RhdHVzKSAlPiUKICBmaWx0ZXIobG9uZyA+IC04My4zICYgbG9uZyA8IC04Mi44KSAlPiUKICBmaWx0ZXIobGF0ID4gNDIuMiAmIGxhdCA8IDQyLjUpICU+JQogIG11dGF0ZShEaXNwb3NpdGlvbiA9IGlmZWxzZShncmVwbCgiXlJlc3BvbnNpYmxlIiwgRGlzcG9zaXRpb24pLCJSZXNwb25zaWJsZSIsIk5vdCBSZXNwb25zaWJsZSBvciBQZW5kaW5nIikpICU+JQogIGdyb3VwX2J5KGFkZHJlc3MpICU+JQogIG11dGF0ZShzZGxhdD1zZChsYXQpLCBzZGxvbmc9c2QobG9uZykpICU+JQogIGZpbHRlcigoc2RsYXQ8MTBlLTQgJiBzZGxvbmc8MTBlLTQpIHwgKGlzLm5hKHNkbGF0KSB8IGlzLm5hKHNkbG9uZykpKSAlPiUKICBncm91cF9ieShhZGRyZXNzLCBEaXNwb3NpdGlvbikgJT4lCiAgbXV0YXRlKG51bV9kaXNwb3NpdGlvbiA9IG4oKSkgJT4lIAogIGdyb3VwX2J5KGFkZHJlc3MpICU+JQogIG11dGF0ZShudW1fdmlvbHMgPSBuKCksIG1heF9hbXQgPSBtYXgoSnVkZ21lbnRBbXQpKSAlPiUKICBmaWx0ZXIoRGlzcG9zaXRpb24gPT0gIlJlc3BvbnNpYmxlIikgJT4lCiAgc3VtbWFyaXNlKGxhdD1tZWRpYW4obGF0KSwgbG9uZz1tZWRpYW4obG9uZyksIG51bV92aW9scyA9IGxhc3QobnVtX3Zpb2xzKSwgCiAgICAgICAgICAgIG51bV9yZXNwb25zaWJsZSA9IGxhc3QobnVtX2Rpc3Bvc2l0aW9uKSwgbWF4X2FtdCA9bGFzdChtYXhfYW10KSkgJT4lCiAgdW5pcXVlKCkgCgpoZWFkKGJsZF9saXN0X3Zpb2wpCmBgYAoKQWRkaW5nIHRoZSB2aW9sYXRpb24gY29kZSBjb3VudHMKYGBge3J9CmJsZF9saXN0X3Zpb2wgJTw+JQogIGxlZnRfam9pbih2aW9sY29kZXNfY291bnRzLCBieT0iYWRkcmVzcyIpCgpjb2xuYW1lcyhibGRfbGlzdF92aW9sKSA8LSBtYWtlLm5hbWVzKGNvbG5hbWVzKGJsZF9saXN0X3Zpb2wpKQpoZWFkKGJsZF9saXN0X3Zpb2wpCmBgYAoKRXhwbG9yaW5nIDMxMSBkYXRhCmBgYHtyfQojY29udmVydGluZyB0byBmYWN0b3JzCmNvbHMgPC0gYygiaXNzdWVfdHlwZSIsICJ0aWNrZXRfc3RhdHVzIikKZGF0YV8zMTEgJTw+JSBtdXRhdGVfZWFjaF8oZnVucyhmYWN0b3IoLikpLGNvbHMpCgojY29udmVydGluZyB0byBkYXRlcwpjb2xzIDwtYygidGlja2V0X2Nsb3NlZF9kYXRlX3RpbWUiLCAiYWNrbm93bGVkZ2VkX2F0IiwgInRpY2tldF9jcmVhdGVkX2RhdGVfdGltZSIsCiAgICAgICAgICJ0aWNrZXRfbGFzdF91cGRhdGVkX2RhdGVfdGltZSIpCmRhdGFfMzExICU8PiUgbXV0YXRlX2VhY2hfKGZ1bnMocGFyc2VfZGF0ZV90aW1lKC4sb3JkZXJzPSJtZFkgSE1TIE9wIix0ej0iQW1lcmljYS9EZXRyb2l0IikpLGNvbHMpCgojZHBseXI6OmdsaW1wc2UoZGF0YV8zMTEpCnN1bW1hcnkoZGF0YV8zMTEpCmBgYAoKVGhlcmUgYXJlIDIzIHR5cGVzIG9mIDMxMSBpc3N1ZXMsIHdlIGNvdWxkIGV4dHJhY3QgY291bnRzIGZvciBlYWNoCmBgYHtyfQp0eXBlczMxMSA8LSBkYXRhXzMxMSAlPiUKICBncm91cF9ieShpc3N1ZV90eXBlKSAlPiUKICBzdW1tYXJpc2UobnVtXzMxMXR5cGUgPSBuKCkpICU+JQogIGFycmFuZ2UoZGVzYyhudW1fMzExdHlwZSkpCnR5cGVzMzExCmBgYAoKR2VuZXJhdGluZyBmZWF0dXJlcyBmb3IgZWFjaCBpc3N1ZSB0eXBlIGFzIGEgY291bnQgcGVyIGFkZHJlc3MKYGBge3J9CnR5cGUzMTFjb3VudHMgPC0gZGF0YV8zMTEgJT4lCiAgc2VsZWN0KGFkZHJlc3MsIGlzc3VlX3R5cGUpICU+JQogIGdyb3VwX2J5KGFkZHJlc3MsIGlzc3VlX3R5cGUpICU+JQogIHN1bW1hcml6ZShudW1fdHlwZTMxMSA9IG4oKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIHNwcmVhZChpc3N1ZV90eXBlLCBudW1fdHlwZTMxMSwgZmlsbCA9IDApIAoKaGVhZCh0eXBlMzExY291bnRzKQpgYGAKCk1vc3QgdGlja2V0cyBhcmUgZWl0aGVyIGFyY2hpdmVkIG9yIGNsb3NlZCwgcHJvYmFibHkgdGhlcmUgaXMgbm8gZGlzY3JpbWluYXRpb24gYXJvdW5kIHRoYXQKYGBge3J9CmRhdGFfMzExICU+JQogIGdyb3VwX2J5KHRpY2tldF9zdGF0dXMpICU+JQogIHN1bW1hcmlzZShudW1fdHhfc3RhdHVzID0gbigpKSAlPiUKICBhcnJhbmdlKGRlc2MobnVtX3R4X3N0YXR1cykpCmBgYApSYXRpbmcgY291bGQgYmUgaW50ZXJlc3RpbmcgdG8gdXNlCmBgYHtyfQpkYXRhXzMxMSAlPiUKICBncm91cF9ieShyYXRpbmcpICU+JQogIHN1bW1hcmlzZShudW1fcmF0aW5nID0gbigpKSAlPiUKICBhcnJhbmdlKGRlc2MobnVtX3JhdGluZykpCmBgYAoKQWxsIGVudHJpZXMgYXJlIGZvciBjaXR5IG9mIERldHJvaXQsIHdoaWNoIGlzIGdvb2QsIGJ1dCB3b24ndCBiZSBkaXNjcmltaW5hdGluZwpgYGB7cn0KZGF0YV8zMTEgJT4lCiAgZ3JvdXBfYnkoY2l0eSkgJT4lCiAgc3VtbWFyaXNlKG51bV9jaXR5ID0gbigpKSAlPiUKICBhcnJhbmdlKGRlc2MobnVtX2NpdHkpKQpgYGAKCkdyb3VwaW5nIGJ5IGFkZHJlc3MsIGl0IHNlZW1zIHRoZXJlIGFyZSBtdWx0aXBsZSBsYXQvbG9uZ3MgcGVyIGFkZHJlc3MsIHJlbW92aW5nIHRob3NlIGVudHJpZXMgdGhhdCBoYXZlIGEgbGF0L2xvbmcgU1RERVYgbGFyZ2VyIHRoYW4gMTBlLTQgKH4xMW0pLCByZW1vdmVzIDYyIGVudHJpZXMuCmBgYHtyfQpibGRfbGlzdF8zMTEgPC0gZGF0YV8zMTEgJT4lCiAgc2VsZWN0KGFkZHJlc3MsIGxhdCwgbG9uZz1sbmcsIHJhdGluZywgdHlwZTMxMSA9IGlzc3VlX3R5cGUsIHJhdGluZykgJT4lCiAgZmlsdGVyKGxvbmcgPiAtODMuMyAmIGxvbmcgPCAtODIuOCkgJT4lCiAgZmlsdGVyKGxhdCA+IDQyLjIgJiBsYXQgPCA0Mi41KSAlPiUKICBncm91cF9ieShhZGRyZXNzKSAlPiUKICBtdXRhdGUoc2RsYXQ9c2QobGF0KSwgc2Rsb25nPXNkKGxvbmcpKSAlPiUKICBmaWx0ZXIoKHNkbGF0PDEwZS00ICYgc2Rsb25nPDEwZS00KSB8IChpcy5uYShzZGxhdCkgfCBpcy5uYShzZGxvbmcpKSkgJT4lCiAgc3VtbWFyaXplKGxhdD1tZWRpYW4obGF0KSwgbG9uZz1tZWRpYW4obG9uZyksIG51bV8zMTEgPSBuKCksIG1heF9yYXRpbmcgPSBtYXgocmF0aW5nKSwgbWluX3JhdGluZz1taW4ocmF0aW5nKSwgZGlmZl9yYXRpbmcgPSBtYXgocmF0aW5nKSAtIG1pbihyYXRpbmcpKSAlPiUKICB1bmlxdWUoKQoKaGVhZChibGRfbGlzdF8zMTEpCmBgYApNb3N0IDMxMSBlbnRyaWVzIGRvbid0IGNoYW5nZSB0aGUgcmF0aW5nCmBgYHtyfQpibGRfbGlzdF8zMTEgJT4lCiAgZ3JvdXBfYnkoZGlmZl9yYXRpbmcpICU+JQogIHRhbGx5KHNvcnQ9VFJVRSkKYGBgCgpBZGRpbmcgdGhlIDMxMSBpc3N1ZSB0eXBlIGNvdW50cwpgYGB7cn0KYmxkX2xpc3RfMzExICU8PiUKICBsZWZ0X2pvaW4odHlwZTMxMWNvdW50cywgYnk9ImFkZHJlc3MiKQoKY29sbmFtZXMoYmxkX2xpc3RfMzExKSA8LSBtYWtlLm5hbWVzKGNvbG5hbWVzKGJsZF9saXN0XzMxMSkpCmhlYWQoYmxkX2xpc3RfMzExKQpgYGAKCkV4cGxvcmluZyBjcmltaW5hbCBpbmNpZGVudHMgaW4gRGV0cm9pdApgYGB7cn0KaGVhZChkYXRhX2NyaW1lKQpgYGAKCmBgYHtyfQojY29udmVydGluZyB0byBmYWN0b3JzCmNvbHMgPC0gYygiQ0FURUdPUlkiLCJTVEFURU9GRkVOU0VGSUxFQ0xBU1MiLCJQUkVDSU5DVCIsIkNPVU5DSUwiLCJORUlHSEJPUkhPT0QiKQpkYXRhX2NyaW1lICU8PiUgbXV0YXRlX2VhY2hfKGZ1bnMoZmFjdG9yKC4pKSxjb2xzKQoKI2NvbnZlcnRpbmcgdG8gZGF0ZXMKY29scyA8LWMoIklOQ0lERU5UREFURSIpCmRhdGFfY3JpbWUgJTw+JSBtdXRhdGVfZWFjaF8oZnVucyhwYXJzZV9kYXRlX3RpbWUoLixvcmRlcnM9Im1kWSBITVMgT3AiLHR6PSJBbWVyaWNhL0RldHJvaXQiKSksY29scykKCiNkcGx5cjo6Z2xpbXBzZShkYXRhX2NyaW1lKQpzdW1tYXJ5KGRhdGFfY3JpbWUpCmBgYApUaGVyZSBhcmUgNTAgY2F0ZWdvcmllcyBvZiBjcmltZXMsIGNvdWxkIGJlIGFkZGVkIGFzIGNvdW50cyB0byB0aGUgZmVhdHVyZXMKYGBge3J9CmNyaW1lX2NhdGVnb3JpZXMgPC0gZGF0YV9jcmltZSAlPiUKICBncm91cF9ieShDQVRFR09SWSkgJT4lCiAgdGFsbHkoc29ydD1UUlVFKQoKY3JpbWVfY2F0ZWdvcmllcwpgYGAKClNlZW1zIHRoYXQgY3JpbWUgaXMgc3ByZWFkIGV2ZW5seSwgZXhjZXB0IGZvciBkaXN0cmljdHMgMyBhbmQgNQpgYGB7cn0KZGF0YV9jcmltZSAlPiUgCiAgZ3JvdXBfYnkoQ09VTkNJTCkgJT4lCiAgdGFsbHkoc29ydD1UUlVFKQpgYGAKCkdyb3VwaW5nIGJ5IGFkZHJlc3MsIGl0IHNlZW1zIHRoZXJlIGFyZSBtdWx0aXBsZSBsYXQvbG9uZ3MgcGVyIGFkZHJlc3MsIGFuZCBhYm91dCAxMDAwIGVudHJpZXMgaGF2ZSBhIGxhdC9sb25nIFNUREVWIGxhcmdlciB0aGFuIDEwZS00ICh+MTFtIGRpc3RhbmNlKSwgc28gSSByZW1vdmVkIHRob3NlCmBgYHtyfQpibGRfbGlzdF9jcmltZSA8LSBkYXRhX2NyaW1lICU+JQogIHNlbGVjdChhZGRyZXNzPUFERFJFU1MsIGxhdD1MQVQsIGxvbmc9TE9OLCB0eXBlY3JpbWUgPSBDQVRFR09SWSkgJT4lCiAgZmlsdGVyKGxvbmcgPiAtODMuMyAmIGxvbmcgPCAtODIuOCkgJT4lCiAgZmlsdGVyKGxhdCA+IDQyLjIgJiBsYXQgPCA0Mi41KSAlPiUKICBncm91cF9ieShhZGRyZXNzKSAlPiUKICBtdXRhdGUoc2RsYXQ9c2QobGF0KSwgc2Rsb25nPXNkKGxvbmcpKSAlPiUKICBmaWx0ZXIoKHNkbGF0PDEwZS00ICYgc2Rsb25nPDEwZS00KSB8IChpcy5uYShzZGxhdCkgfCBpcy5uYShzZGxvbmcpKSkgJT4lCiAgc3VtbWFyaXplKGxhdD1tZWRpYW4obGF0KSwgbG9uZz1tZWRpYW4obG9uZyksIG51bV9jcmltZSA9IG4oKSkgJT4lCiAgdW5pcXVlKCkKCmhlYWQoYmxkX2xpc3RfY3JpbWUpCmBgYAoKR2VuZXJhdGluZyBjcmltZSBmZWF0dXJlcyBmb3IgZWFjaCBpc3N1ZSB0eXBlIGFzIGEgY291bnQgcGVyIGFkZHJlc3MKYGBge3J9CmNyaW1lX2NhdGVnb3J5X2NvdW50cyA8LSBkYXRhX2NyaW1lICU+JQogIHNlbGVjdChhZGRyZXNzPUFERFJFU1MsIENBVEVHT1JZKSAlPiUKICBncm91cF9ieShhZGRyZXNzLCBDQVRFR09SWSkgJT4lCiAgc3VtbWFyaXplKG51bV9jYXRlZ29yeSA9IG4oKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIHNwcmVhZChDQVRFR09SWSwgbnVtX2NhdGVnb3J5LCBmaWxsID0gMCkgCgpoZWFkKGNyaW1lX2NhdGVnb3J5X2NvdW50cykKYGBgCgpBZGRpbmcgdGhlIGNyaW1lIGNhdGVnb3J5IGNvdW50cwpgYGB7cn0KYmxkX2xpc3RfY3JpbWUgJTw+JQogIGxlZnRfam9pbihjcmltZV9jYXRlZ29yeV9jb3VudHMsIGJ5PSJhZGRyZXNzIikKCmNvbG5hbWVzKGJsZF9saXN0X2NyaW1lKSA8LSBtYWtlLm5hbWVzKGNvbG5hbWVzKGJsZF9saXN0X2NyaW1lKSkKaGVhZChibGRfbGlzdF9jcmltZSkKYGBgCgpQcmVwYXJlIGEgbGlzdCBvZiBjb29yZGluYXRlcyBieSB0eXBlIG9mIHJlY29yZCwgYXQgcHJlY2lzaW9uIG9mIDQgZGlnaXRzICh+MTFtIGFjY3VyYWN5KSBSRUY6IGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0RlY2ltYWxfZGVncmVlcyNQcmVjaXNpb24KYGBge3J9CmJsZF9saXN0X2Nvb3JkIDwtIGJsZF9saXN0XzMxMSAlPiUKICBtdXRhdGUodHlwZSA9ICIzMTEiKSAlPiUKICBzZWxlY3QodHlwZSwgbGF0LCBsb249bG9uZywgYWRkcmVzcykgJT4lCiAgYmluZF9yb3dzKHNlbGVjdChtdXRhdGUoYmxkX2xpc3RfY3JpbWUsIHR5cGU9ImNyaW1lIiksIHR5cGUsIGxhdCwgbG9uPWxvbmcsIGFkZHJlc3MpKSAlPiUKICBiaW5kX3Jvd3Moc2VsZWN0KG11dGF0ZShibGRfbGlzdF92aW9sLCB0eXBlPSJ2aW9sIiksIHR5cGUsIGxhdCwgbG9uPWxvbmcsIGFkZHJlc3MpKSAlPiUKICBtdXRhdGUoY29vcmQ9cGFzdGUobGF0LGxvbixzZXA9IiwiKSkgJT4lCiAgYXJyYW5nZShkZXNjKGxhdCksIGRlc2MobG9uKSkKICAKYmxkX2xpc3RfY29vcmQKYGBgCgpMZXQncyBwbG90IGFsbCByZWNvcmRzIG9mIDMxMSBjYWxscywgYmxpZ2h0IHZpb2xhdGlvbnMsIGFuZCBjcmltZSByZXBvcnRzIGluIGEgbWFwIGNlbnRlcmVkIGluIERldHJvaXQuIFRoZSByZWNvcmRzIGFyZSBncm91cGVkIGJ5IGRpc3RhbmNlLiBXaGVuIHpvb21pbmcgaW4sIHdlIHNlZSB0aGF0IGxhdC9sb25nIGNvb3JkaW5hdGVzIHRoYXQgYXJlIGFib3V0ICsvLTAuMDAwMiBkZWdyZWVzIGFwcGFydCBzZWVtIHRvIGJlbG9uZyB0byB0aGUgc2FtZSBsb2NhdGlvbgpgYGB7cn0KbGlicmFyeShsZWFmbGV0KQoKaWNvbnVybCA8LSAiaHR0cHM6Ly9kb2NzLmdvb2dsZS5jb20vZHJhd2luZ3MvZC8xMXZjeFFESDVEUXN0SEZ1VWYwVmhvWG9XRng1YjhlbGZac2VYUExjYnNtRS9wdWI/dz01MCZoPTUwIgoKbGVhZmxldCgpICU+JSAKICBzZXRWaWV3KGxhdCA9IDQyLjM3LCBsbmcgPSAtODMuMTAsIHpvb20gPSAxMSkgJT4lIAogIGFkZFRpbGVzKGdyb3VwID0gIk9TTSAoZGVmYXVsdCkiKSAlPiUKICBhZGRNYXJrZXJzKGRhdGEgPSBmaWx0ZXIoYmxkX2xpc3RfY29vcmQsdHlwZT09IjMxMSIpLCBsbmcgPSB+bG9uLCBsYXQgPSB+bGF0LAogICAgICAgICAgICAgY2x1c3Rlck9wdGlvbnMgPSBtYXJrZXJDbHVzdGVyT3B0aW9ucygpLCBncm91cCA9ICIzMTEgY2FsbHMiLAogICAgICAgICAgICAgcG9wdXAgPSB+Y29vcmQsIGxhYmVsID0gfnR5cGUpICU+JQogIGFkZE1hcmtlcnMoZGF0YSA9IGZpbHRlcihibGRfbGlzdF9jb29yZCx0eXBlPT0iY3JpbWUiKSwgbG5nID0gfmxvbiwgbGF0ID0gfmxhdCwKICAgICAgICAgICAgIGNsdXN0ZXJPcHRpb25zID0gbWFya2VyQ2x1c3Rlck9wdGlvbnMoKSwgZ3JvdXAgPSAiY3JpbWUiLAogICAgICAgICAgICAgcG9wdXAgPSB+Y29vcmQsIGxhYmVsID0gfnR5cGUpICU+JQogIGFkZE1hcmtlcnMoZGF0YSA9IGZpbHRlcihibGRfbGlzdF9jb29yZCx0eXBlPT0idmlvbCIpLCBsbmcgPSB+bG9uLCBsYXQgPSB+bGF0LAogICAgICAgICAgICAgY2x1c3Rlck9wdGlvbnMgPSBtYXJrZXJDbHVzdGVyT3B0aW9ucygpLCBncm91cCA9ICJibGlnaHQgdmlvbCIsCiAgICAgICAgICAgICBwb3B1cCA9IH5jb29yZCwgbGFiZWwgPSB+dHlwZSkgJT4lCiAgYWRkTWFya2VycyhkYXRhID0gYmxkX2xpc3RfcGVybWl0LCBsbmcgPSB+bG9uZywgbGF0ID0gfmxhdCwgCiAgICAgICAgICAgICBjbHVzdGVyT3B0aW9ucyA9IG1hcmtlckNsdXN0ZXJPcHRpb25zKCksIGdyb3VwID0gImRlbW9saXRpb24iLCAKICAgICAgICAgICAgIHBvcHVwID0gfmFkZHJlc3MsIGxhYmVsID0gImRlbW9saXRpb24iLAogICAgICAgICAgICAgaWNvbiA9IGxpc3QoaWNvblVybCA9IGljb251cmwsIGljb25zaXplID0gYygxLDEpKSkgJT4lCiAgYWRkTGF5ZXJzQ29udHJvbCgKICAgIG92ZXJsYXlHcm91cHMgPSBjKCIzMTEgY2FsbHMiLCAiY3JpbWUiLCAiYmxpZ2h0IHZpb2wiLCJkZW1vbGl0aW9uIiksCiAgICBvcHRpb25zID0gbGF5ZXJzQ29udHJvbE9wdGlvbnMoY29sbGFwc2VkID0gRkFMU0UpCiAgKQoKYGBgCgpMZXQncyBidWlsZCBhIGxpc3Qgb2YgcG90ZW50aWFsIGxvY2F0aW9ucyB0byBleHBsb3JlIGJ5IGdyb3VwaW5nIGFsbCBsYXQvbG9uZyBjb29yZGluYXRlcyB0aGF0IGFyZSBhcm91bmQgKy8tMC4wMDAyIGRlZ3JlZXMgYXBwYXJ0LiBUaGlzIGFwcHJvYWNoIHlpZWxkcyB0byA2MDY3OSBidWlsZGluZ3Mgb3V0IG9mIDE0Njg5MiByZWNvcmRzLiAKYGBge3J9CiMgQWxsb3dhYmxlIGVycm9yCmVfbGF0ID0gMC4wMDAyCmVfbG9uID0gMC4wMDAyCgpibGRfbGlzdF9hbGxfZmlsZW5hbWUgPC0gImV4dHJhY3RlZF9ibGRfbGlzdF9lbGF0Mi1lbG9uMi5jc3YiCgojIENhbGN1bGF0aW9uIHRha2VzIH4zMCBtaW4sIHNraXAgaXQgaWYgZmlsZSBhYm92ZSBleGlzdHMgdG8gc3BlZWQgdXAKaWYoIGZpbGUuZXhpc3RzKGJsZF9saXN0X2FsbF9maWxlbmFtZSkgKSB7CiAgYmxkX2xpc3RfYWxsIDwtIHJlYWRfY3N2KGJsZF9saXN0X2FsbF9maWxlbmFtZSkKCiMgZmlsZSBkb2Vzbid0IGV4aXN0cywgbGV0J3MgY3JhbmsgdGhlIGNhbGN1bGF0aW9uIHVwIQp9IGVsc2UgewoKICBpIDwtIDAKICBvdXQgPC0gZGF0YV9mcmFtZSgpCiAgCiAgaW5wdXQgPC0gYmxkX2xpc3RfY29vcmQgJT4lCiAgICBtdXRhdGUoY29vcmRfYXNzaWduZWQgPSAwKQogIAogIGwgPC0gbnJvdyhpbnB1dCkKICAKICB3aGlsZShsID4gMCkgewogICAgaW5wdXQgJTw+JSAKICAgICAgZmlsdGVyKGNvb3JkX2Fzc2lnbmVkICE9IDEpCiAgICAKICAgIGx0IDwtIGlucHV0WzEsXSRsYXQKICAgIGxuIDwtIGlucHV0WzEsXSRsb24KICAgIAogICAgaW5wdXQgJTw+JQogICAgICBtdXRhdGUoY29vcmRfYXNzaWduZWQgPSBpZmVsc2UoIChhYnMobG9uLWxuKTw9ZV9sb24gJiBhYnMobGF0LWx0KTw9ZV9sYXQpLCAxLCAwKSApCiAgICAKICAgIGkgPC0gaSsxCiAgICAKICAgIG91dCA8LSBpbnB1dCAlPiUgCiAgICAgIGZpbHRlcihjb29yZF9hc3NpZ25lZCA9PSAxKSAlPiUKICAgICAgbXV0YXRlKGJsZF9pZCA9IGksIGJsZF9sYXQgPSBtZWRpYW4obGF0KSwgYmxkX2xvbiA9IG1lZGlhbihsb24pKSAlPiUKICAgICAgYmluZF9yb3dzKG91dCkKICAgIAogICAgbCA8LSBucm93KGlucHV0KQogICAgaWYobCA9PSAwKSBicmVhawogICAgaWYoaSUlMTAwPT0wKSBwcmludChwYXN0ZTAoaSwiIGl0ZXJhdGlvbnMgIixsLCIgcmVjb3JkcyBsZWZ0IikpCiAgfSAKICBibGRfbGlzdF9hbGwgPC0gb3V0CiAgb3V0IDwtIE5VTEwKICB3cml0ZV9jc3YoYmxkX2xpc3RfYWxsLCBwYXRoID0gYmxkX2xpc3RfYWxsX2ZpbGVuYW1lKQp9CmBgYAoKTGV0J3Mgam9pbiBpbiB0aGUgYnVpbGRpbmcgaWRlbnRpZmllciB0byB0aGUgZXhpc3RpbmcgcmVjb3JkcwpgYGB7cn0KYmxkX2xpc3RfY3JpbWUgPC0gYmxkX2xpc3RfYWxsICU+JSAKICBmaWx0ZXIodHlwZSA9PSAiY3JpbWUiKSAlPiUKICBzZWxlY3QoYmxkX2lkLCBibGRfbGF0LCBibGRfbG9uLCBhZGRyZXNzKSAlPiUKICBsZWZ0X2pvaW4oYmxkX2xpc3RfY3JpbWUsIGJ5PSJhZGRyZXNzIikgJT4lIAogIHNlbGVjdCgtbGF0LCAtbG9uZykgJT4lCiAgcmVuYW1lKGxhdD1ibGRfbGF0LCBsb25nPWJsZF9sb24pCgpibGRfbGlzdF8zMTEgPC0gYmxkX2xpc3RfYWxsICU+JSAKICBmaWx0ZXIodHlwZSA9PSAiMzExIikgJT4lCiAgc2VsZWN0KGJsZF9pZCwgYmxkX2xhdCwgYmxkX2xvbiwgYWRkcmVzcykgJT4lCiAgbGVmdF9qb2luKGJsZF9saXN0XzMxMSwgYnk9ImFkZHJlc3MiKSAlPiUKICBzZWxlY3QoLWxhdCwgLWxvbmcpICU+JQogIHJlbmFtZShsYXQ9YmxkX2xhdCwgbG9uZz1ibGRfbG9uKQoKYmxkX2xpc3RfdmlvbCA8LSBibGRfbGlzdF9hbGwgJT4lIAogIGZpbHRlcih0eXBlID09ICJ2aW9sIikgJT4lCiAgc2VsZWN0KGJsZF9pZCwgYmxkX2xhdCwgYmxkX2xvbiwgYWRkcmVzcykgJT4lCiAgbGVmdF9qb2luKGJsZF9saXN0X3Zpb2wsIGJ5PSJhZGRyZXNzIikgJT4lCiAgc2VsZWN0KC1sYXQsIC1sb25nKSAlPiUKICByZW5hbWUobGF0PWJsZF9sYXQsIGxvbmc9YmxkX2xvbikKYGBgCgpSZWNhbGN1bGF0ZSBzdW1tYXJpZXMgYXQgdGhlIGJ1aWxkaW5nIGxldmVsIGZvciBjcmltZQpgYGB7cn0KdG1wIDwtIGJsZF9saXN0X2NyaW1lICU+JQogIHNlbGVjdCgtYWRkcmVzcykgJT4lCiAgc2VsZWN0KC1sYXQsIC1sb25nKSAlPiUKICBncm91cF9ieShibGRfaWQpICU+JSAKICBzdW1tYXJpc2VfZWFjaChmdW5zKHN1bSkpCgpibGRfbGlzdF9jcmltZSAlPD4lIAogIHNlbGVjdChibGRfaWQsIGxhdCwgbG9uZykgJT4lIAogIGRpc3RpbmN0KCkgJT4lCiAgbGVmdF9qb2luKHRtcCwgYnk9ImJsZF9pZCIpCgpoZWFkKGJsZF9saXN0X2NyaW1lKQpgYGAKClJlY2FsY3VsYXRlIHN1bW1hcmllcyBhdCB0aGUgYnVpbGRpbmcgbGV2ZWwgZm9yIDMxMQpgYGB7cn0KdG1wIDwtIGJsZF9saXN0XzMxMSAlPiUKICBzZWxlY3QoLWFkZHJlc3MpICU+JQogIHNlbGVjdCgtbGF0LCAtbG9uZywgLW1pbl9yYXRpbmcsIC1tYXhfcmF0aW5nLCAtZGlmZl9yYXRpbmcpICU+JQogIGdyb3VwX2J5KGJsZF9pZCkgJT4lIAogIHN1bW1hcmlzZV9lYWNoKGZ1bnMoc3VtKSkKCnRtcCA8LSBibGRfbGlzdF8zMTEgJT4lIAogIHNlbGVjdChibGRfaWQsIG1pbl9yYXRpbmcsIG1heF9yYXRpbmcpICU+JQogIGdyb3VwX2J5KGJsZF9pZCkgJT4lIAogIHN1bW1hcmlzZShtaW5fcmF0aW5nID0gbWluKG1pbl9yYXRpbmcpLCBtYXhfcmF0aW5nID0gbWF4KG1heF9yYXRpbmcpKSAlPiUKICBtdXRhdGUoZGlmZl9yYXRpbmcgPSBtYXhfcmF0aW5nIC0gbWluX3JhdGluZykgJT4lCiAgbGVmdF9qb2luKHRtcCwgYnk9ImJsZF9pZCIpCgpibGRfbGlzdF8zMTEgJTw+JSAKICBzZWxlY3QoYmxkX2lkLCBsYXQsIGxvbmcpICU+JSAKICBkaXN0aW5jdCgpICU+JQogIGxlZnRfam9pbih0bXAsIGJ5PSJibGRfaWQiKQoKaGVhZChibGRfbGlzdF8zMTEpCmBgYAoKUmVjYWxjdWxhdGUgc3VtbWFyaWVzIGF0IHRoZSBidWlsZGluZyBsZXZlbCBmb3IgYmxpZ2h0IHZpb2xhdGlvbnMKYGBge3J9CnRtcCA8LSBibGRfbGlzdF92aW9sICU+JQogIHNlbGVjdCgtYWRkcmVzcykgJT4lCiAgc2VsZWN0KC1sYXQsIC1sb25nLCAtbWF4X2FtdCkgJT4lCiAgZ3JvdXBfYnkoYmxkX2lkKSAlPiUgCiAgc3VtbWFyaXNlX2VhY2goZnVucyhzdW0pKQoKdG1wIDwtIGJsZF9saXN0X3Zpb2wgJT4lIAogIHNlbGVjdChibGRfaWQsIG1heF9hbXQpICU+JQogIGdyb3VwX2J5KGJsZF9pZCkgJT4lIAogIHN1bW1hcmlzZShtYXhfYW10ID0gbWF4KG1heF9hbXQpKSAlPiUKICBsZWZ0X2pvaW4odG1wLCBieT0iYmxkX2lkIikKCmJsZF9saXN0X3Zpb2wgJTw+JSAKICBzZWxlY3QoYmxkX2lkLCBsYXQsIGxvbmcpICU+JSAKICBkaXN0aW5jdCgpICU+JQogIGxlZnRfam9pbih0bXAsIGJ5PSJibGRfaWQiKQoKaGVhZChibGRfbGlzdF92aW9sKQpgYGAKCkRpcmVjdCBqb2luIGJ5IGJ1aWxkaW5nIGlkZW50aWZpZXIsIGJsZF9pZApgYGB7cn0KYmxkX2xpc3RfY3JpbWVfMzExX3Zpb2wgPC0gYmxkX2xpc3RfY3JpbWUgJT4lCiAgZnVsbF9qb2luKGJsZF9saXN0XzMxMSwgYnkgPSAiYmxkX2lkIikgJT4lCiAgZnVsbF9qb2luKGJsZF9saXN0X3Zpb2wsIGJ5ID0gImJsZF9pZCIpIAoKYmxkX2xpc3RfY3JpbWVfMzExX3Zpb2wgJTw+JSAKICBtdXRhdGUobGF0ID0gaWZlbHNlKGlzLm5hKGxhdCksaWZlbHNlKGlzLm5hKGxhdC54KSxsYXQueSxsYXQueCksbGF0KSkgJT4lCiAgbXV0YXRlKGxvbmcgPSBpZmVsc2UoaXMubmEobG9uZyksaWZlbHNlKGlzLm5hKGxvbmcueCksbG9uZy55LGxvbmcueCksbG9uZykpICU+JQogIHNlbGVjdCgtbGF0LngsIC1sb25nLngsIC1sYXQueSwgLWxvbmcueSkgJT4lCiAgc2VsZWN0KGJsZF9pZCwgbGF0LCBsb25nLCBldmVyeXRoaW5nKCkpIAoKaGVhZChibGRfbGlzdF9jcmltZV8zMTFfdmlvbCkKYGBgCgpBZGRpbmcgc3Vycm91bmRpbmcgc3RhdGlzdGljcywgMC4wMDEgfiAxMTFtCmBgYHtyfQpibGRfbGlzdF9jcmltZV8zMTFfdmlvbCAlPD4lIAogIG11dGF0ZShjb29yZF9uZWlnaCA9IHBhc3RlKHJvdW5kKGxhdCxkaWdpdHM9Mykscm91bmQobG9uZyxkaWdpdHM9MykpKSAlPiUKICBncm91cF9ieShjb29yZF9uZWlnaCkgJT4lCiAgbXV0YXRlKAogICAgbnVtX2NyaW1lX25laWdoID0gc3VtKG51bV9jcmltZSksCiAgICBudW1fMzExX25laWdoID0gc3VtKG51bV8zMTEpLAogICAgYXZnX21heF9yYXRpbmdfbmVpZ2ggPSBtZWFuKG1heF9yYXRpbmcpLAogICAgYXZnX21pbl9yYXRpbmdfbmVpZ2ggPSBtZWFuKG1pbl9yYXRpbmcpLAogICAgbnVtX3Zpb2xzX25laWdoID0gc3VtKG51bV92aW9scyksCiAgICBudW1fcmVzcG9uc19uZWlnaCA9IHN1bShudW1fcmVzcG9uc2libGUpLAogICAgYXZnX21heF9hbXRfbmVpZ2ggPSBtZWFuKG1heF9hbXQpLAogICAgdG90YWxfbWF4X2FtdF9uZWlnaCA9IHN1bShtYXhfYW10KQogICkgJT4lCiAgdW5ncm91cCgpCgpoZWFkKGJsZF9saXN0X2NyaW1lXzMxMV92aW9sKQpgYGAKClRha2luZyBjYXJlIG9mIE5BJ3MsIGRvaW5nIHNvbWUgZHVtbXkgaW1wdXRhdGlvbgpgYGB7cn0KY29kZXN2aW9sIDwtIG1ha2UubmFtZXModW5saXN0KGxhcHBseSh1bmlxdWUodmlvbENvZGVzX21hbnVhbF9jYXRlZ29yaXphdGlvbiRWaW9sR3JvdXApLCBhcy5jaGFyYWN0ZXIpKSkKY29kZXMzMTEgPC0gbWFrZS5uYW1lcyh1bmxpc3QobGFwcGx5KHR5cGVzMzExJGlzc3VlX3R5cGUsIGFzLmNoYXJhY3RlcikpKQpjb2Rlc2NyaW1lIDwtIG1ha2UubmFtZXModW5saXN0KGxhcHBseShjcmltZV9jYXRlZ29yaWVzJENBVEVHT1JZLCBhcy5jaGFyYWN0ZXIpKSkKCmNvbHMgPC0gYygibnVtX2NyaW1lIiwgIm51bV8zMTEiLCAibnVtX3Zpb2xzIiwgIm51bV9yZXNwb25zaWJsZSIsCiAgICAgICAgICAibnVtX2NyaW1lX25laWdoIiwgIm51bV8zMTFfbmVpZ2giLCAibnVtX3Zpb2xzX25laWdoIiwgCiAgICAgICAgICAibnVtX3Jlc3BvbnNfbmVpZ2giLCAidG90YWxfbWF4X2FtdF9uZWlnaCIpCmNvbHMgPC0gYyhjb2xzLCBjb2Rlc3Zpb2wsIGNvZGVzMzExLCBjb2Rlc2NyaW1lKQpibGRfbGlzdF9jcmltZV8zMTFfdmlvbCAlPD4lIAogIG11dGF0ZV9lYWNoXyhmdW5zKGlmZWxzZShpcy5uYSguKSwwLC4pKSxjb2xzKQoKY29scyA8LSBjKCJtYXhfcmF0aW5nIiwgIm1pbl9yYXRpbmciLCAiZGlmZl9yYXRpbmciLCAibWF4X2FtdCIsIAogICAgICAgICAgImF2Z19tYXhfcmF0aW5nX25laWdoIiwgImF2Z19taW5fcmF0aW5nX25laWdoIiwgImF2Z19tYXhfYW10X25laWdoIikKYmxkX2xpc3RfY3JpbWVfMzExX3Zpb2wgJTw+JSBtdXRhdGVfZWFjaF8oZnVucyhpZmVsc2UoaXMubmEoLiksLTEsLikpLGNvbHMpCgojTGV0J3MgcHJpbnQgdGhlIGNvbHVtbnMgdGhhdCBzdGlsbCBjb250YWluIE5BJ3MsIGlmIGNoYXJhY3RlcigwKSB0aGVuIHdlIGFyZSBnb29kIQpvdXQgPC0gY29sbmFtZXMoYmxkX2xpc3RfY3JpbWVfMzExX3Zpb2wpW2NvbFN1bXMoaXMubmEoYmxkX2xpc3RfY3JpbWVfMzExX3Zpb2wpKSA+IDBdCmlmZWxzZShsZW5ndGgob3V0KT09MCxwcmludCgiTm8gTkEgaXMgbGVmdCA6KSIpLG91dCkKYGBgCgpUaGUgbWFzaHVwIHJlc3VsdHMgaW4gYWJvdXQgNDIxMCBibGlnaHRlZCBlbnRyaWVzLCBvdXQgb2YgODIxNjMgdG90YWwgYnVpbGRpbmdzLgpgYGB7cn0KbW9kZWxpbmdfZGF0YSA8LSBibGRfbGlzdF9jcmltZV8zMTFfdmlvbCAKCm1vZGVsaW5nX2RhdGEgJTw+JQogIHJvd3dpc2UoKSAlPiUKICBtdXRhdGUobmJsaWdodGVkID0gaW5fYmxpZ2h0KGxhdCwgbG9uZywgaW5kYXRhX2RlZ3JlZXMpKSAKCm1vZGVsaW5nX2RhdGEgJT4lCiAgZmlsdGVyKG5ibGlnaHRlZCA+IDApICU+JSAKICBucm93KCkKYGBgCgpDcmVhdGluZyBkZXBlbmRlbnQgdmFyaWFibGUgImNvbmRpdGlvbiIgYXMgYm9vbGVhbiBmcm9tICJuYmxpZ2h0ZWQiICh0aGlzIGRlc2NyaWJlcyB0aGUgbnVtYmVyIG9mIHJlY29yZHMgYmxpZ2h0ZWQgaW4gdGhlIHNhbWUgbGF0L2xvbmcpCmBgYHtyfQptb2RlbGluZ19kYXRhICU8PiUKICBtdXRhdGUoY29uZGl0aW9uID0gZmFjdG9yKGlmX2Vsc2UobmJsaWdodGVkID4gMCwgIkJMSUdIVEVEIiwgIk5PVF9CTElHSFRFRCIpKSkgJT4lCiAgc2VsZWN0KC1ibGRfaWQsIC1sYXQsIC1sb25nLCAtbmJsaWdodGVkLCAtY29vcmRfbmVpZ2gpCgp0YWJsZShtb2RlbGluZ19kYXRhJGNvbmRpdGlvbikKYGBgCkJhbGFuY2luZyB0aGUgdHdvIGNsYXNzZXMgYnkgZG93bnNhbXBsaW5nIHRoZSBudW1iZXIgb2YgY2FzZXMgb2Ygbm9uLWJsaWdodCAobm90IGlkZWFsLCBidXQgaGVscHMgbW9kZWxpbmcgYW5kIGNvbXB1dGUgdGltZSkKYGBge3J9Cm1vZF9kYXRhX3NhbXBsZWQgPC0gbW9kZWxpbmdfZGF0YSAlPiUKICBmaWx0ZXIoY29uZGl0aW9uID09ICJCTElHSFRFRCIpCgptb2RfZGF0YV9zYW1wbGVkIDwtIG1vZGVsaW5nX2RhdGEgJT4lCiAgZmlsdGVyKGNvbmRpdGlvbiA9PSAiTk9UX0JMSUdIVEVEIikgJT4lIAogIHNhbXBsZV9uKDU1MDApICU+JQogIGJpbmRfcm93cyhtb2RfZGF0YV9zYW1wbGVkKQoKdGFibGUobW9kX2RhdGFfc2FtcGxlZCRjb25kaXRpb24pCmBgYAoKQ3JlYXRpbmcgYSBzZXQtYXNpZGUgZGF0YXNldCBmb3IgdGVzdCwgYW5kIGEgbW9kZWxpbmcgc2V0CmBgYHtyfQpzZXQuc2VlZCgxMDcpCmluVGVzdCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKHk9bW9kX2RhdGFfc2FtcGxlZCRjb25kaXRpb24sIHA9MC4xLCBsaXN0PUZBTFNFKQp0ZXN0U2V0IDwtIG1vZF9kYXRhX3NhbXBsZWRbaW5UZXN0LF0KbW9kZWxTZXQgPC0gbW9kX2RhdGFfc2FtcGxlZFstaW5UZXN0LF0KdGFibGUodGVzdFNldCRjb25kaXRpb24pCmBgYApDcmVhdGluZyBhIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIGRhdGEgc2V0cyBmb3IgbW9kZWwgdHJhaW5pbmcgYW5kIHZhbGlkYXRpb24sIHRoZSB0cmFpbmluZyBzZXQgaXMgYWJvdXQgOC40ayByZWNvcmRzCmBgYHtyfQpzZXQuc2VlZCgxMDcpCmluVmFsaWQgPC0gY3JlYXRlRGF0YVBhcnRpdGlvbih5PW1vZGVsU2V0JGNvbmRpdGlvbiwgcD0wLjE1LCBsaXN0PUZBTFNFKQp0cmFpblNldCA8LSBtb2RfZGF0YV9zYW1wbGVkWy1pblZhbGlkLF0KdmFsaWRTZXQgPC0gbW9kX2RhdGFfc2FtcGxlZFtpblZhbGlkLF0KI3dlIGFyZSBhY3R1YWxseSB1c2luZyB0aGUgdmFsaWRhdGlvbiBzZXQgYXMgYSB0ZXN0c2V0LCBzbyBhZGRpbmcgdGhlbSB0b2dldGhlciB0byAKI2hhdmUgYSBiZXR0ZXIgcGVyZm9ybWFuY2UgZXN0aW1hdGUsIHdlJ2xsIHVzZSBjcm9zcy12YWxpZGF0aW9uIHRvIGNob29zZSB0aGUgYmVzdCBtb2RlbAp2YWxpZFNldCAlPD4lIGJpbmRfcm93cyh0ZXN0U2V0KQp0YWJsZSh0cmFpblNldCRjb25kaXRpb24pCmBgYApBcyBmb3IgdGhlIHZhbGlkYXRpb24gc2V0LCBpdCBpcyBhYm91dCAxLjJrIHJlY29yZHMKYGBge3J9CnRhYmxlKHZhbGlkU2V0JGNvbmRpdGlvbikKYGBgClBsb3R0aW5nIHRoZSBoaXN0b2dyYW0gb2YgbnVtYmVyIG9mIHZpb2xhdGlvbnMgZm9yIGJvdGggYmxpZ2h0ZWQgYW5kIG5vdCBibGlnaHRlZCBidWlsZGluZ3MsIHdlIHNlZSBzb21lIGRpZmZlcmVuY2UgdGhhdCBtaWdodCBjb250cmlidXRlIHRvIGRpc2NyaW1pbmF0ZSB0aGUgMiBjbGFzc2VzCmBgYHtyfQpwIDwtIGZpZ3VyZSh3aWR0aCA9IDYwMCwgaGVpZ2h0ID0gMzUwKSAlPiUgCiAgbHlfaGlzdChudW1fdmlvbHMsIGRhdGEgPSB0cmFpblNldFt3aGljaCh0cmFpblNldCRjb25kaXRpb24gPT0gIk5PVF9CTElHSFRFRCIpLF0sIGNvbG9yID0gImJsdWUiLCBhbHBoYSA9IDAuMjUsIGZyZXEgPSBGLCBicmVha3MgPSAyNSkgJT4lCiAgbHlfaGlzdChudW1fdmlvbHMsIGRhdGEgPSB0cmFpblNldFt3aGljaCh0cmFpblNldCRjb25kaXRpb24gPT0gIkJMSUdIVEVEIiksXSwgY29sb3IgPSAicmVkIiwgYWxwaGEgPSAwLjI1LCBmcmVxID0gRiwgYnJlYWtzID0gMjUpIApwCmBgYApMZXQncyB0cmFpbiBhIGdlbmVyYWwgbGluZWFyIG1vZGVsIChHTE0pIHdpdGggNS1mb2xkIGNyb3NzLXZhbGlkYXRpb24gdHJ5aW5nIHRvIHByZWRpY3QgdGhlIGJsaWd0aCBjb25kaXRpb24gb25seSBieSB0aGUgbnVtYmVyIG9mIHZpb2xhdGlvbnMgcmVwb3J0ZWQgaW4gdGhlIGNvb3JkaW5hdGUsIHdoaWNoIHlpZWxkcyA3Mi4zJSBBVUMgb24gdGhlIFJPQywgODQuMiUgdHJ1ZSBwb3NpdGl2ZSwgYnV0IDM3LjclIHRydWUgbmVnYXRpdmUgcmF0ZXMuIApgYGB7cn0KIyA1IGZvbGQgY3Jvc3MtdmFsaWRhdGlvbiwgMSByZXBldGl0aW9uCmN0cmwgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwKICAgICAgICAgICAgICAgICAgICAgbnVtYmVyID0gNSwKICAgICAgICAgICAgICAgICAgICAgcmVwZWF0cyA9IDEsCiAgICAgICAgICAgICAgICAgICAgIGNsYXNzUHJvYnMgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICBzdW1tYXJ5RnVuY3Rpb24gPSB0d29DbGFzc1N1bW1hcnkKICAgICAgICAgICAgICAgICAgICAgKQoKIyBnZW5lcmFsIGxpbmVhciBtb2RlbApnbG1Nb2RlbCA8LSB0cmFpbihjb25kaXRpb24gfiBudW1fdmlvbHMsCiAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpblNldCwKICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImdsbSIsCiAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGN0cmwsCiAgICAgICAgICAgICAgICAgIG1ldHJpYyA9ICJST0MiCiAgICAgICAgICAgICAgICAgICkKZ2xtTW9kZWwKYGBgCgpBIGRlY2lzaW9uIHRyZWUgaGFzIGEgc2ltaWxhciBwZXJmb3JtYW5jZSwgNzEuOCUgQVVDIG9uIFJPQyBmb3IgY3Jvc3MtdmFsaWRhdGlvbiwgYnV0IGF0IGEgbW9yZSBiYWxhbmNlZCBvcGVyYXRpbmcgcG9pbnQsIHdpdGggNjUuMCUgdHJ1ZSBwb3NpdGl2ZSBhbmQgNjguNyUgdHJ1ZSBuZWdhdGl2ZS4KYGBge3J9CiMgZGVjaXNpb24gdHJlZSBtb2RlbApkdHJlZU1vZGVsIDwtIHRyYWluKGNvbmRpdGlvbiB+IG51bV92aW9scywKICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluU2V0LAogICAgICAgICAgICAgICAgICBtZXRob2QgPSAicnBhcnQiLAogICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBjdHJsLAogICAgICAgICAgICAgICAgICBtZXRyaWMgPSAiUk9DIgogICAgICAgICAgICAgICAgICApCmR0cmVlTW9kZWwKYGBgCgpBIHJhbmRvbSBmb3Jlc3QgeWllbGRzIHNsaWdobHR5IGxvd2VyIHJlc3V0cywgd2l0aCBhbiA3MS4wJSBBVUMgb24gUk9DLCBidXQgYWxzbyBhdCBhIGJhbGFuY2VkIG9wZXJhdGluZyBwb2ludCwgNjUuNSUgdHJ1ZSBwb3NpdGl2ZSwgYW5kIDY4LjAlIHRydWUgbmVnYXRpdmUKYGBge3J9CiMgcmFuZG9tIGZvcmVzdCBtb2RlbApyZk1vZGVsIDwtIHRyYWluKGNvbmRpdGlvbiB+IG51bV92aW9scywKICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluU2V0LAogICAgICAgICAgICAgICAgICBtZXRob2QgPSAicmYiLAogICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBjdHJsLAogICAgICAgICAgICAgICAgICBtZXRyaWMgPSAiUk9DIgogICAgICAgICAgICAgICAgICApCnJmTW9kZWwKYGBgCgpDb21wYXJpbmcgdGhlIG1vZGVscyBhY3Jvc3MgY3Jvc3MtdmFsaWRhdGlvbiByZXNhbXBsZXMsIHdlIHNlZSBhIHNpbWlsYXIgcGVyZm9ybWFuY2UgaW4gdGVybXMgb2YgUk9DLiBUaGUgR0xNIHNlZW1zIHRvIGFjaGlldmUgdGhlIGhpZ2hlc3QgdHJ1ZSBwb3NpdGl2ZSwgYnV0IGF0IGEgY29zdCBvZiBhIGxvdyB0cnVlIG5lZ2F0aXZlLiAKYGBge3J9CnJlc2FtcHMgPC0gcmVzYW1wbGVzKGxpc3QoZ2xtID0gZ2xtTW9kZWwsIGR0cmVlID0gZHRyZWVNb2RlbCwgcmYgPSByZk1vZGVsKSkKc3VtbWFyeShyZXNhbXBzKQpgYGAKT3RoZXIgdmFyaWFibGVzL2ZlYXR1cmVzOgpgYGB7cn0KdmFyY3JpbWUgPC0gIm51bV9jcmltZSIKdmFyMzExIDwtIHBhc3RlKCJtaW5fcmF0aW5nIiwgIm1heF9yYXRpbmciLCAiZGlmZl9yYXRpbmciLCJudW1fMzExIixzZXAgPSAiKyIpCnZhcnZpb2xzIDwtIHBhc3RlKCJtYXhfYW10IiwibnVtX3Zpb2xzIiwibnVtX3Jlc3BvbnNpYmxlIixzZXAgPSAiKyIpCnZhcm5laWdoIDwtIHBhc3RlKCJudW1fY3JpbWVfbmVpZ2giLCAibnVtXzMxMV9uZWlnaCIsImF2Z19tYXhfcmF0aW5nX25laWdoIiwgCiAgICAgICAgICAgICAgICAgICJhdmdfbWluX3JhdGluZ19uZWlnaCIsICJudW1fdmlvbHNfbmVpZ2giLCAibnVtX3Jlc3BvbnNfbmVpZ2giLCAKICAgICAgICAgICAgICAgICAgImF2Z19tYXhfYW10X25laWdoIiwgInRvdGFsX21heF9hbXRfbmVpZ2giLHNlcD0iKyIpCgp2YXIzMTFjb2RlcyA8LSBwYXN0ZShjb2RlczMxMSwgY29sbGFwc2UgPSAiKyIpCnZhcmNyaW1lY29kZXMgPC0gcGFzdGUoY29kZXNjcmltZSwgY29sbGFwc2UgPSAiKyIpCnZhcnZpb2xjb2RlcyA8LSBwYXN0ZShjb2Rlc3Zpb2wsIGNvbGxhcHNlID0gIisiKQoKZl9iYXNpYyA8LSBhcy5mb3JtdWxhKHBhc3RlKCJjb25kaXRpb24gfiAiLCBwYXN0ZSh2YXJjcmltZSwgdmFyMzExLCB2YXJ2aW9scywgc2VwPSIrIikpKQpmX2Jhc2ljX25laWdoIDwtIGFzLmZvcm11bGEocGFzdGUoImNvbmRpdGlvbiB+ICIsIHBhc3RlKHZhcmNyaW1lLCB2YXIzMTEsIHZhcnZpb2xzLCB2YXJuZWlnaCwgc2VwPSIrIikpKQpmX2FsbCA8LSBhcy5mb3JtdWxhKHBhc3RlKCJjb25kaXRpb24gfiAiLCBwYXN0ZSh2YXJjcmltZSwgdmFyMzExLCB2YXJ2aW9scywgdmFybmVpZ2gsIHZhcjMxMWNvZGVzLCB2YXJjcmltZWNvZGVzLCB2YXJ2aW9sY29kZXMsIHNlcD0iKyIpKSkKZl9hbGxfbm9uZWlnaCA8LSBhcy5mb3JtdWxhKHBhc3RlKCJjb25kaXRpb24gfiAiLCBwYXN0ZSh2YXJjcmltZSwgdmFyMzExLCB2YXJ2aW9scywgdmFyMzExY29kZXMsIHZhcmNyaW1lY29kZXMsIHZhcnZpb2xjb2Rlcywgc2VwPSIrIikpKQoKYGBgCgoKVGhlIGZhY3QgdGhhdCB0aGUgcmFuZG9tIGZvcmVzdCBwZXJmb3JtYW5jZSBpbiB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBzZXRzIHdhcyBzaW1pbGFyLCBlbHVkZXMgdGhhdCB0aGUgbW9kZWwgaW4gbm90IGNvbXBsZXggZW5vdWdoLiAKCkFkZGluZyAzMTEgY2FsbCBhbmQgY3JpbWUgZGF0YSwgeWllbGRzIHRvIGEgc2xpZ2h0bHkgYmV0dGVyIEFVQyBvZiA3Mi4wJSAoZnJvbSA3MS44JSkKYGBge3J9CiMgZGVjaXNpb24gdHJlZSBtb2RlbApkdHJlZU1vZGVsMiA8LSB0cmFpbihmX2Jhc2ljLAogICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5TZXQsCiAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJycGFydCIsCiAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGN0cmwsCiAgICAgICAgICAgICAgICAgIG1ldHJpYyA9ICJST0MiLAogICAgICAgICAgICAgICAgICBuYS5hY3Rpb249bmEuZXhjbHVkZQogICAgICAgICAgICAgICAgICApCmR0cmVlTW9kZWwyCmBgYAoKQWRkaW5nIHRoZSBuZWlnaGJvcmhvb2QgZmVhdHVyZXMgc2VlbXMgdG8gZHJvcCB0aGUgcGVyZm9ybWFuY2Ugc2xpZ2h0bHkgdG8gNzEuNCUgQVVDIChmcm9tIDcyLjAlIGFib3ZlKQpgYGB7cn0KIyBkZWNpc2lvbiB0cmVlIG1vZGVsCmR0cmVlTW9kZWwzIDwtIHRyYWluKGZfYmFzaWNfbmVpZ2gsCiAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpblNldCwKICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJwYXJ0IiwKICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gY3RybCwKICAgICAgICAgICAgICAgICAgbWV0cmljID0gIlJPQyIsCiAgICAgICAgICAgICAgICAgIG5hLmFjdGlvbj1uYS5leGNsdWRlCiAgICAgICAgICAgICAgICAgICkKZHRyZWVNb2RlbDMKYGBgCgpBZGRpbmcgYWxsIGZlYXR1cmVzIHNsaWdodCB3b3JzZXMgdGhlIHBlcmZvcm1hbmNlIHRvIDcwLjglQVVDCmBgYHtyfQojIGRlY2lzaW9uIHRyZWUgbW9kZWwKZHRyZWVNb2RlbDQgPC0gdHJhaW4oZl9hbGwsCiAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpblNldCwKICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJwYXJ0IiwKICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gY3RybCwKICAgICAgICAgICAgICAgICAgbWV0cmljID0gIlJPQyIsCiAgICAgICAgICAgICAgICAgIG5hLmFjdGlvbj1uYS5leGNsdWRlCiAgICAgICAgICAgICAgICAgICkKZHRyZWVNb2RlbDQKYGBgClJlbW92aW5nIHRoZSBuZWlnaGJvcmhvb2QgZmVhdHVyZXMgZnJvbSBhbGwgYnJpbmdzIHRoZSBwZXJmb3JtYW5jZSBiYWNrIHVwIHNsaWdodGx5IHRvIDcxLjglIEFVQwpgYGB7cn0KIyBkZWNpc2lvbiB0cmVlIG1vZGVsCmR0cmVlTW9kZWw1IDwtIHRyYWluKGZfYWxsX25vbmVpZ2gsCiAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpblNldCwKICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJwYXJ0IiwKICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gY3RybCwKICAgICAgICAgICAgICAgICAgbWV0cmljID0gIlJPQyIsCiAgICAgICAgICAgICAgICAgIG5hLmFjdGlvbj1uYS5leGNsdWRlCiAgICAgICAgICAgICAgICAgICkKZHRyZWVNb2RlbDUKYGBgCkNvbXBhcmluZyB0aGUgbW9kZWxzOgpgYGB7cn0KcmVzYW1wcyA8LSByZXNhbXBsZXMobGlzdChkdHJlZV92aW9sb25seSA9IGR0cmVlTW9kZWwsIGR0cmVlX3BsdXMzMTFjcmltZSA9IGR0cmVlTW9kZWwyLCBkdHJlZV9wbHVzbmVpZ2ggPSBkdHJlZU1vZGVsMywgZHRyZWVfcGx1c2NvZGVzID0gZHRyZWVNb2RlbDQsIGR0cmVlX2FsbF9taW51c25laWdoID0gZHRyZWVNb2RlbDUpKQpzdW1tYXJ5KHJlc2FtcHMpCmBgYAoKTGV0J3MgdHJ5IGEgcmFuZG9tIGZvcmVzdCB3aXRoIGFsbCB0aGUgdmFyaWFibGVzLiBCZWluZyBhIG1vcmUgY29tcGxleCBtb2RlbCwgaXQgc2VlbXMgdG8gcmVhY2ggdG8gYSBuZXcgYmVzdCBwZXJmb3JtYW5jZSBvZiA3My4yJSBBVUMgb24gUk9DICgxLjIlIGJldHRlciB0aGFuIHRoZSBiZXN0IGR0cmVlKSwgd2l0aCA2MS44JSBUcnVlIFBvc2l0aXZlIChUUCksIGFuZCA3My45JSBUcnVlIE5lZ2F0aXZlIChUTikKYGBge3J9CiMgcmFuZG9tIGZvcmVzdCBtb2RlbApyZk1vZGVsMiA8LSB0cmFpbihjb25kaXRpb24gfiAuLAogICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5TZXQsCiAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJyZiIsCiAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGN0cmwsCiAgICAgICAgICAgICAgICAgIG1ldHJpYyA9ICJST0MiCiAgICAgICAgICAgICAgICAgICkKcmZNb2RlbDIKYGBgCgpgYGB7cn0KcmYydmFySW1wIDwtIHZhckltcChyZk1vZGVsMiwgc2NhbGUgPSBGQUxTRSkKcGxvdChyZjJ2YXJJbXAsIHRvcD0yOCkKYGBgClJldHJhaW5pbmcgUmFuZG9tIEZvcmVzdCB3aXRoIG9ubHkgbW9zdCBpbXBvcnRhbnQgZmVhdHVyZXMuIFVzaW5nIHRoZSB0b3AgMTcgZmVhdHVyZXMsIHRoZSBwZXJmb3JtYW5jZSBkcm9wcyAwLjAxJSB0byA3Mi45JSBBVUMgKGNvbXBhcmVkIHRvIHRoZSBSRiBtb2RlbCB3aXRoIDEwMSBmZWF0dXJlcykKYGBge3J9CnRyYWluU2V0MiA8LSB0cmFpblNldCAlPiUgc2VsZWN0KG51bV92aW9scywgbWF4X2FtdCwgbnVtX3Jlc3BvbnNpYmxlLCBjb21wbGlhbmNlLCBoYXphcmQsIG51bV9jcmltZSwgd2FzdGUsIG90aGVyLCBudW1fcmVzcG9uc19uZWlnaCwgdG90YWxfbWF4X2FtdF9uZWlnaCwgbnVtX3Zpb2xzX25laWdoLCBhdmdfbWF4X2FtdF9uZWlnaCwgZGlmZl9yYXRpbmcsIG51bV9jcmltZV9uZWlnaCwgbWluX3JhdGluZywgbnVtXzMxMSwgbWF4X3JhdGluZywgY29uZGl0aW9uKQojIHJhbmRvbSBmb3Jlc3QgbW9kZWwKcmZNb2RlbDMgPC0gdHJhaW4oY29uZGl0aW9uIH4gLiwKICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluU2V0MiwKICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJmIiwKICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gY3RybCwKICAgICAgICAgICAgICAgICAgbWV0cmljID0gIlJPQyIKICAgICAgICAgICAgICAgICAgKQpyZk1vZGVsMwpgYGAKCldlJ2xsIHRyeSBhbHNvIGdyYWRpZW50IGJvb3N0aW5nLiBMZXQncyBnZXQgc3RhcnRlZCBwcmVwcGluZyB0aGUgZGF0YSBmb3IgWEdCb29zdApgYGB7cn0KZHRyYWluIDwtIE1hdHJpeDo6c3BhcnNlLm1vZGVsLm1hdHJpeChjb25kaXRpb24gfiAuLCBkYXRhPXRyYWluU2V0KQpkZXZhbCA8LSBNYXRyaXg6OnNwYXJzZS5tb2RlbC5tYXRyaXgoY29uZGl0aW9uIH4gLiwgZGF0YT12YWxpZFNldCkKYGBgCgpUcmFpbmluZyBhbmQgY3Jvc3MtdmFsaWRhdGlvbiBvZiBHQk0gd2l0aCBYR0Jvb3N0Ckt1ZG9zIHRvIEphbWVzIE1hcnF1ZXogZm9yIHRoZSBhd2Vzb21lIFhHQm9vc3QgcmVjaXBlClNlZSBoZXJlOiB3d3cuamFtZXNtYXJxdWV6cG9ydGZvbGlvLmNvbQpgYGB7cn0KZ3JpZCA8LSBleHBhbmQuZ3JpZChucm91bmRzID0gYygzMDAsIDQwMCwgNTAwLCA2MDApLAogICAgICAgICAgICAgICAgICAgIG1heF9kZXB0aCA9IGMoMywgNSwgNywgOSksCiAgICAgICAgICAgICAgICAgICAgZXRhID0gYygwLjA1LCAwLjEsIDAuMiwgMC4zKSwKICAgICAgICAgICAgICAgICAgICBnYW1tYSA9IDEsCiAgICAgICAgICAgICAgICAgICAgY29sc2FtcGxlX2J5dHJlZSA9IGMoMC41LCAxKSwKICAgICAgICAgICAgICAgICAgICBtaW5fY2hpbGRfd2VpZ2h0ID0gMSwKICAgICAgICAgICAgICAgICAgICBzdWJzYW1wbGUgPSAxKQoKY3RybCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwKICAgICAgICAgICAgICAgICAgICAgbnVtYmVyID0gNSwKICAgICAgICAgICAgICAgICAgICAgc3VtbWFyeUZ1bmN0aW9uID0gdHdvQ2xhc3NTdW1tYXJ5LAogICAgICAgICAgICAgICAgICAgICBjbGFzc1Byb2JzID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgYWxsb3dQYXJhbGxlbCA9IFRSVUUgKQoKc2V0LnNlZWQoMTA3KQpzeXN0ZW0udGltZSh4Z2JUdW5lIDwtIHRyYWluKHggPSBkdHJhaW4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IGZhY3Rvcih0cmFpblNldCRjb25kaXRpb24pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJ4Z2JUcmVlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRyaWMgPSAiUk9DIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGdyaWQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gY3RybCkpCmBgYApHcmFkaWVudCBib29zdGluZyByZWFjaGVzIHRoZSBiZXN0IHBlcmZvcm1hbmNlIGluIGNyb3NzdmFsaWRhdGlvbiwgb2YgNzMuNSUgUk9DL0FVQywgd2l0aCBhIDYzLjYlIHRydWUgcG9zaXRpdmUsIGFuZCA3MS4yJSB0cnVlIG5lZ2F0aXZlIHJhdGVzLiBUaGUgb3B0aW1hbCBoeXBlcnBhcm1ldGVycyBiZWluZzogbnJvdW5kcyA9IDMwMCwgbWF4X2RlcHRoID0gMywgZXRhID0gMC4wNSwgZ2FtbWEgPSAxLCBjb2xzYW1wbGVfYnl0cmVlID0gMSwgbWluX2NoaWxkX3dlaWdodCA9IDEgYW5kIHN1YnNhbXBsZSA9IDEuCmBgYHtyfQp4Z2JUdW5lCgpnZ3Bsb3QoeGdiVHVuZSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQpgYGAKVHJhaW5pbmcgYSBHQk0gd2l0aCB0aGUgYmVzdCBwYXJhbWV0ZXJzLCB5aWVsZHMgdG8gNzguNyUgQVVDIApgYGB7cn0KdHJhaW5sYWIgPC0gdHJhaW5TZXQgJT4lCiAgc2VsZWN0KGNvbmRpdGlvbikgJT4lCiAgbXV0YXRlKGxhYiA9IGlmZWxzZSgoY29uZGl0aW9uPT0iQkxJR0hURUQiKSwxLDApKSAlPiUKICBzZWxlY3QobGFiKQoKcGFyYW0gPC0gbGlzdChvYmplY3RpdmUgPSAnYmluYXJ5OmxvZ2lzdGljJywKICAgICAgICAgICAgICBldmFsX21ldHJpYyA9ICdhdWMnLAogICAgICAgICAgICAgIG1heF9kZXB0aCA9IDMsIAogICAgICAgICAgICAgIGV0YSA9IDAuMDUsIAogICAgICAgICAgICAgIGdhbW1hID0gMSwgCiAgICAgICAgICAgICAgY29sc2FtcGxlX2J5dHJlZSA9IDEsIAogICAgICAgICAgICAgIG1pbl9jaGlsZF93ZWlnaHQgPSAxLAogICAgICAgICAgICAgIHN1YnNhbXBsZSA9IDEpCgpzZXQuc2VlZCgxMDcpCnN5c3RlbS50aW1lKHhnYiA8LSB4Z2Jvb3N0KHBhcmFtcyA9IHBhcmFtLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGR0cmFpbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSB0cmFpbmxhYiRsYWIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3VuZHMgPSAzMDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHByaW50X2V2ZXJ5X24gPSAxMDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSAxKSkKYGBgCkJlbG93IGlzIGEgcGxvdCBvZiBmZWF0dXJlIGltcG9ydGFuY2UKYGBge3J9Cm1vZGVsIDwtIHhnYi5kdW1wKHhnYiwgd2l0aF9zdGF0cyA9IFRSVUUpCm5hbWVzIDwtIGRpbW5hbWVzKGR0cmFpbilbWzJdXQppbXBvcnRhbmNlX21hdHJpeCA8LSB4Z2IuaW1wb3J0YW5jZShuYW1lcywgbW9kZWwgPSB4Z2IpWzA6MzBdCnhnYi5wbG90LmltcG9ydGFuY2UoaW1wb3J0YW5jZV9tYXRyaXgpCmBgYAoKUGxvdHRpbmcgdGhlIFJPQywgd2UgY2FuIGV4cGxvcmUgZGlmZmVyZW50IHZhbHVlcyBvZiB0cnVlIHBvc2l0aXZlIGFuZCBmYWxzZSBwb3NpdGl2ZSByYXRlcy4gCmBgYHtyfQp2YWxpZGxhYiA8LSB2YWxpZFNldCAlPiUKICBzZWxlY3QoY29uZGl0aW9uKSAlPiUKICBtdXRhdGUobGFiID0gaWZlbHNlKChjb25kaXRpb249PSJCTElHSFRFRCIpLDEsMCkpICU+JQogIHNlbGVjdChsYWIpCgp4Z2JWYWwgPC0gcHJlZGljdCh4Z2IsIG5ld2RhdGEgPSBkZXZhbCkKCnhnYi5wcmVkIDwtIFJPQ1I6OnByZWRpY3Rpb24oeGdiVmFsLCB2YWxpZGxhYiRsYWIpCnhnYi5wZXJmIDwtIFJPQ1I6OnBlcmZvcm1hbmNlKHhnYi5wcmVkLCAidHByIiwgImZwciIpCmF1YyA8LSBST0NSOjpwZXJmb3JtYW5jZSh4Z2IucHJlZCwiYXVjIikKYXVjIDwtIHVubGlzdChzbG90KGF1YywgInkudmFsdWVzIikpCmF1Yzwtcm91bmQoYXVjLCBkaWdpdHMgPSAzKQphdWN0IDwtIHBhc3RlKGMoIkFVQyAgPSAiKSxhdWMsc2VwPSIiKQoKcGxvdCh4Z2IucGVyZiwKICAgICBhdmc9InRocmVzaG9sZCIsCiAgICAgY29sb3JpemU9VFJVRSwKICAgICBsd2Q9MywKICAgICBwcmludC5jdXRvZmZzLmF0PXNlcSgwLCAxLCBieT0wLjA1KSwKICAgICB0ZXh0LmFkaj1jKC0wLjUsIDAuNSksCiAgICAgdGV4dC5jZXg9MC42KQpncmlkKGNvbD0ibGlnaHRncmF5IikKYXhpcygxLCBhdD1zZXEoMCwgMSwgYnk9MC4xKSkKYXhpcygyLCBhdD1zZXEoMCwgMSwgYnk9MC4xKSkKYWJsaW5lKHY9YygwLjEsIDAuMywgMC41LCAwLjcsIDAuOSksIGNvbD0ibGlnaHRncmF5IiwgbHR5PSJkb3R0ZWQiKQphYmxpbmUoaD1jKDAuMSwgMC4zLCAwLjUsIDAuNywgMC45KSwgY29sPSJsaWdodGdyYXkiLCBsdHk9ImRvdHRlZCIpCmxpbmVzKHg9YygwLCAxKSwgeT1jKDAsIDEpLCBjb2w9ImJsYWNrIiwgbHR5PSJkb3R0ZWQiKQoKI2FkZGluZyBtaW4vbWF4IEFVQyBpbiB0aGUgcGxvdApsZWdlbmQoMC41LDAuNSxjKGF1Y3QpLGJvcmRlcj0id2hpdGUiLGNleD0xLjIsYm94LmNvbCA9ICJ3aGl0ZSIpCmBgYAoKTGV0J3MgY2hlY2sgdGhlIHJlc3VsdHMgaW4gdGhlIHZhbGlkYXRpb24gc2V0IGZvciB0aGUgb3B0aW1hbCBvcGVyYXRpbmcgcG9pbnQsIGFyb3VuZCAwLjQ3IHlpZWxkcyB0byA3NC43JSBUUCBhbmQgNjMuOSUgVE4KYGBge3J9CnhnYlZhbCA8LSBwcmVkaWN0KHhnYiwgbmV3ZGF0YSA9IGRldmFsKQp4Z2JWYWwucmVzcCA8LSBpZmVsc2UoeGdiVmFsID4gMC41LCAxLCAwKQpjb25mdXNpb25NYXRyaXgoeGdiVmFsLnJlc3AsIHZhbGlkbGFiJGxhYiwgcG9zaXRpdmUgPSAnMScpCmBgYAoKT3RoZXIgZGF0YSB0byBleHBsb3JlIGluIHRoZSBmdXR1cmU6Ci0gTmVpZ2hib29yaG9vZC9yZWdpb24KLSBEZW1vZ3JhcGhpYyBkYXRhCi0gTUxTIC8gWmlsbG93IChsYXN0IHRpbWUgc29sZCwgcHJpY2UsICMgc29sZCBob3VzZXMgYXJvdW5kKQotIERldHJvaXQgUGFyY2VsIGRhdGEK